Python实战笔记(三) 多线程 下

简介: Python实战笔记(三) 多线程

正文


4、维护线程安全


由于不同线程之间是并行的,如果多个线程同时修改一个数据,那么结果将会是不可预料的

import threading
import time
num = 0
def add(val):
    global num
    time.sleep(1)
    num += val
    print(num)
def main():
    for index in range(1, 9):
        worker = threading.Thread(target = add, args = (index,))
        worker.start()
if __name__ == '__main__':
    main()

程序每次运行的结果都是未知的,所以我们需要采取一些机制,使线程能够按照期望的方式工作


(1)锁对象:threading.Lock 与 threading.RLock


一个 threading.Lock 对象只有两种状态,锁定 (locked) 和非锁定 (unlocked)


任意一个线程可以使用 acquire() 方法将锁对象设置为锁定状态  (称为获得锁)


若此时有其它线程调用 acquire() 方法,那么该线程将会被阻塞


直至其它任意线程使用 release() 方法将锁对象设置为非锁定状态 (称为释放锁)


但如果在调用 release() 方法时,锁对象处于非锁定状态,则会抛出异常

锁对象状态 调用的方法 结果
unlocked acquire 将锁对象设置为锁定状态
locked acquire 阻塞当前线程
locked release 将锁对象设置为非锁定状态
unlocked release 抛出异常


threading.Lock 有两个常用的方法,分别是 acquire()release()


acquire(blocking = True, timeout = -1):获得锁


  • blocking:是否阻塞线程,默认为 True,表示没有获得锁时,将会阻塞当前线程
  • timeout  :最长阻塞时间,默认为 -1    ,表示一直阻塞下去,直至锁被释放


release():释放锁


import threading
import time
num = 0
lock = threading.Lock() # 声明锁对象
def add(val):
    lock.acquire() # 修改数据前,将锁对象设置为锁定状态
    global num
    time.sleep(1)
    num += val
    print(num)
    lock.release() # 修改数据后,将锁对象设置为非锁定状态
def main():
    for index in range(1, 8):
        worker = threading.Thread(target = add, args = (index,))
        worker.start()
if __name__ == '__main__':
    main()

threading.RLockthreading.Lock 的功能大致一样,但 threading.RLock 的特别之处在于:


  • 在同一线程内,多次调用 acquire() 方法,不会阻塞线程
  • 使用多少次 acquire() 方法获得锁,就必须使用多少次 release() 方法释放锁
  • 某线程通过 acquire() 方法获得锁,只允许该线程通过 release() 方法释放锁


(2)信号量对象:threading.Semaphore


一个 threading.Semaphore 对象在内部维护一个计数器,规定计数器的值不能小于 0


任意一个线程可以使用 acquire() 方法,使得计数器减 1


如果此时计数器已经为 0,那么将会阻塞当前线程,直至计数器大于 0


任意一个线程可以使用 release() 方法,使得计数器加 1

计数器 调用的方法 结果
大于 0 acquire 使计数器减 1
等于 0 acquire 阻塞当前线程
大于等于 0 release 使计数器加 1


threading.Semaphore 有两个常用的方法,分别是 acquire()release()


acquire(blocking = True, timeout = -1):使计数器减 1


  • blocking:是否阻塞线程,默认为 True,表示计数器为 0 时,将会阻塞当前线程
  • timeout  :最长阻塞时间,默认为 -1    ,表示一直阻塞下去 ,直至计数器大于 0


release():使计数器加 1


import threading
import time
num = 0
semaphore = threading.Semaphore(1) # 声明信号量,可以指定计数器初始值,默认为 1
def add(val):
    semaphore.acquire() # 使计数器减 1
    global num
    time.sleep(1)
    num += val
    print(num)
    semaphore.release() # 使计数器加 1
def main():
    for index in range(1, 8):
        worker = threading.Thread(target = add, args = (index,))
        worker.start()
if __name__ == '__main__':
    main()

使用信号量还可以使多个线程同时修改一个数据

import threading
import time
semaphore = threading.Semaphore(3)
def run():
    semaphore.acquire()
    time.sleep(1)
    print(threading.current_thread().name, 'Running')
    semaphore.release()
def main():
    for _ in range(7):
        worker = threading.Thread(target = run)
        worker.start()
if __name__ == '__main__':
    main()


(3)条件对象:threading.Condition


条件对象在锁对象的基础上封装而成,threading.Condition 常用的方法如下:


acquire():获得锁,调用底层(Lock 或 RLock)所对应的函数


release():释放锁,调用底层(Lock 或 RLock)所对应的函数


wait(timeout = None)

在调用该方法后,调用 release() 释放锁,然后阻塞当前线程,等待其它线程调用 notify() 唤醒

然后在被唤醒后,调用 acquire() 尝试获得锁

若有设置 timeout,即使没有其它线程调用 notify() 唤醒当前线程,也会在超时之后自动被唤醒


wait_for(predicate, timeout = None)

在调用该方法后,首先调用 predicate,若返回 True  ,则继续执行

若返回 False,调用 release() 释放锁,然后阻塞当前线程,等待其它线程调用 notify() 唤醒

然后在被唤醒后,也会调用 predicate,若返回 False,将会一直阻塞下去

若返回 True  ,调用 acquire() 尝试获得锁

若有设置 timeout,即使没有其它线程调用 notify() 唤醒当前线程,也会在超时之后自动被唤醒


notify(n = 1):唤醒 n 个线程

notify_all()  :唤醒所有线程


import threading
import time
data = 1
condition = threading.Condition()
def isEven():
    global data
    return data % 2 == 0
def wait():
    condition.acquire()        # 要先获得锁,才能释放锁
    print('wait_thread 进入等待')
    condition.wait_for(isEven) # 释放锁,阻塞当前线程,等待唤醒后重新获得锁,继续执行
    print('wait_thread 继续执行')
    condition.release()        # 重新获得锁后,记得要释放锁
def wake():
    global data
    condition.acquire() # 要先获得锁,再修改数据
    data = 2
    print('唤醒 wait_thread')
    condition.notify()
    condition.release() # 获得锁后,要释放锁
def main():
    wait_thread = threading.Thread(target = wait)
    wake_thread = threading.Thread(target = wake)
    wait_thread.start()
    time.sleep(1)
    wake_thread.start()
if __name__ == '__main__':
    main()
# 执行结果
# wait_thread 进入等待
# 唤醒 wait_thread
# wait_thread 继续执行


(4)事件对象:threading.Event


一个 threading.Event 对象在内部会维护一个标记,初始时默认为 False

threading.Event 常用的方法如下:


  • set()      :将标记设置为 True
  • clear()  :将标记设置为 False
  • wait()    :阻塞当前线程,直到标记变为 True
  • is_set():标记是否为 True
import threading
import time
event = threading.Event()
def wait():
    print(threading.current_thread().name, '进入等待')
    event.wait()
    print(threading.current_thread().name, '继续执行')
def wake():
    print('唤醒所有线程')
    event.set()
def main():
    for _ in range(5):
        wait_thread = threading.Thread(target = wait)
        wait_thread.start()
    time.sleep(1)
    wake_thread = threading.Thread(target = wake)
    wake_thread.start()
if __name__ == '__main__':
    main()
# 执行结果
# Thread-1 进入等待
# Thread-2 进入等待
# Thread-3 进入等待
# Thread-4 进入等待
# Thread-5 进入等待
# 唤醒所有线程
# Thread-1 继续执行
# Thread-2 继续执行
# Thread-5 继续执行
# Thread-4 继续执行
# Thread-3 继续执行

文章知识点与官方知识档案匹配,可进一步学习相关知识

目录
相关文章
|
14天前
|
并行计算 数据处理 调度
Python中的并发编程:探索多线程与多进程的奥秘####
本文深入探讨了Python中并发编程的两种主要方式——多线程与多进程,通过对比分析它们的工作原理、适用场景及性能差异,揭示了在不同应用需求下如何合理选择并发模型。文章首先简述了并发编程的基本概念,随后详细阐述了Python中多线程与多进程的实现机制,包括GIL(全局解释器锁)对多线程的影响以及多进程的独立内存空间特性。最后,通过实例演示了如何在Python项目中有效利用多线程和多进程提升程序性能。 ####
|
15天前
|
数据采集 机器学习/深度学习 人工智能
Python编程入门:从基础到实战
【10月更文挑战第36天】本文将带你走进Python的世界,从基础语法出发,逐步深入到实际项目应用。我们将一起探索Python的简洁与强大,通过实例学习如何运用Python解决问题。无论你是编程新手还是希望扩展技能的老手,这篇文章都将为你提供有价值的指导和灵感。让我们一起开启Python编程之旅,用代码书写想法,创造可能。
|
17天前
|
数据库 Python
异步编程不再难!Python asyncio库实战,让你的代码流畅如丝!
在编程中,随着应用复杂度的提升,对并发和异步处理的需求日益增长。Python的asyncio库通过async和await关键字,简化了异步编程,使其变得流畅高效。本文将通过实战示例,介绍异步编程的基本概念、如何使用asyncio编写异步代码以及处理多个异步任务的方法,帮助你掌握异步编程技巧,提高代码性能。
51 4
|
16天前
|
机器学习/深度学习 数据可视化 数据处理
Python数据科学:从基础到实战
Python数据科学:从基础到实战
25 1
|
17天前
|
机器学习/深度学习 JSON API
Python编程实战:构建一个简单的天气预报应用
Python编程实战:构建一个简单的天气预报应用
33 1
|
19天前
|
前端开发 API 开发者
Python Web开发者必看!AJAX、Fetch API实战技巧,让前后端交互如丝般顺滑!
在Web开发中,前后端的高效交互是提升用户体验的关键。本文通过一个基于Flask框架的博客系统实战案例,详细介绍了如何使用AJAX和Fetch API实现不刷新页面查看评论的功能。从后端路由设置到前端请求处理,全面展示了这两种技术的应用技巧,帮助Python Web开发者提升项目质量和开发效率。
33 1
|
19天前
|
缓存 测试技术 Apache
告别卡顿!Python性能测试实战教程,JMeter&Locust带你秒懂性能优化💡
告别卡顿!Python性能测试实战教程,JMeter&Locust带你秒懂性能优化💡
36 1
|
8天前
|
数据采集 Java Python
爬取小说资源的Python实践:从单线程到多线程的效率飞跃
本文介绍了一种使用Python从笔趣阁网站爬取小说内容的方法,并通过引入多线程技术大幅提高了下载效率。文章首先概述了环境准备,包括所需安装的库,然后详细描述了爬虫程序的设计与实现过程,包括发送HTTP请求、解析HTML文档、提取章节链接及多线程下载等步骤。最后,强调了性能优化的重要性,并提醒读者遵守相关法律法规。
40 0
|
12天前
|
数据采集 存储 数据处理
探索Python中的异步编程:从基础到实战
【10月更文挑战第39天】在编程世界中,时间就是效率的代名词。Python的异步编程特性,如同给程序穿上了一双翅膀,让它们在执行任务时飞得更高、更快。本文将带你领略Python异步编程的魅力,从理解其背后的原理到掌握实际应用的技巧,我们不仅会讨论理论基础,还会通过实际代码示例,展示如何利用这些知识来提升你的程序性能。准备好让你的Python代码“起飞”了吗?让我们开始这场异步编程的旅程!
27 0
|
15天前
|
并行计算 数据挖掘 大数据
Python数据分析实战:利用Pandas处理大数据集
Python数据分析实战:利用Pandas处理大数据集
下一篇
无影云桌面