目 录
Python 多线程编程目录
Python 多线程编程-01-threading 模块初识
Python 多线程编程-02-threading 模块-锁的使用
Python 多线程编程-03-threading 模块 - Condition
Python 多线程编程-04-threading 模块 - Event
Python 多线程编程-05-threading 模块 - Semaphore 和 BoundedSemaphore
Python 多线程编程-06-threading 模块 - Timer
Python 多线程编程-07-threading 模块 - Barrier
1. 复杂线程同步
前面两章已经说过 threading 模块中互斥锁的使用,不管是 threading.Lock 还是 threading.RLock,这种互斥锁是最简单的线程同步机制,在实际工作中会有很多复杂情况是互斥锁无法解决的。而Python 提供的 Condition 对象提供了对复杂线程同步问题的支持。
1.1 生产者消费者问题
常见的生产者消费者模型具体来讲,就是在一个系统中,存在生产者和消费者两种角色,它们通过内存缓冲区进行通信,生产者生产消费者需要的资料,消费者消耗生成的资料,生产者和消费者之间共享的内存缓冲池大小可变,生产者数量可变,消费者数量可变。
像这类问题,用简单的互斥锁就比较难以解决。
1.2 threading.Conditon 基本原理
Condition 被称为条件变量,可以理解为一种进阶的锁,使用的基本原理如下:
1、Condition对象的构造函数可以接受一个 Lock/RLock 对象作为参数,如果没有指定,则默认是可重入锁 RLock,生成的 Condition 对象会维护一个 Lock/RLock 和一个 waiting 池。
2、线程调用 acquire 方法获得 Condition 对象,这一点和 Lock/RLock类似 。
# wait() 方法、notify() 方法、notifiy_all() 方法都只能在线程调用 acquire 方法之后,线程调用 release 方法之前!
3、线程调用 wait 方法时,线程会释放 Condition 内部的锁并进入 blocked 状态,同时在waiting 池中记录这个线程。
4、线程调用 notify/notify_all 方法时,Condition 对象会从 waiting 池中挑选一个线程,通知其调用 acquire 方法尝试取到锁。线程只能通过notify方法唤醒,所以notifyAll的作用在于防止有的线程永远处于沉默状态。
5、线程调用 release 方法释放 Condition 对象
2. threading.Condition
2.1 threading.Conditon 属性和方法
Condition 被称为条件变量,除了提供与 Lock、RLock 类似的 acquire() 和 release() 方法外,还提供了 wait() 、notify()、notifyAll() 方法。
序号 | 属性和方法 | 描述 |
1 | acquire (blocking=True) | 锁住这个 Condidtion,然后返回一个布尔值。 `blocking` 指示这个 Condition 无法锁定时候,是否阻塞线程。 如果 `blocking` 是 False 且另外一个线程锁定了这个 Conditon,那么立刻返回 False。 如果 `blocking` 是 True 且另外一个线程锁定了这个 Conditon,那么则阻塞这个线程直到 Condition 被释放,获得之并返回 True。阻塞过程是可以被打断。 这意味着 Conditon 是可重入的。 |
2 | release() | 释放这个 Condition,允许在阻塞等待队列中的其他线程获得该 Condition。这个 Condition 此刻必须在被锁定的状态,且是被同一个线程给锁定的,否则返回 RuntimeError。 请注意,如果这个 Condition 被同一个线程锁定了多次,那么也需要 release 多次才行。 |
3 | notify(n=1) | 唤醒一个或者多个在等待这个 Condition 的线程。如果被唤醒的线程没有acquire 这个 Condition,那么报 RuntimeError。 这个方法最多唤醒 n 个 线程。如果没有线程等待,则无作为。 |
4 | notifyAll() | 唤醒所有在等待这个 Condition 的线程。如果被唤醒的线程没有锁定这个 Condition,那么报 RuntimeError。 |
5 | wait (timeout=None) | 阻塞等待直到该 Condition 释放或者 timeout 到期。 如果被唤醒的线程没有acquire 这个 Condition,那么报 RuntimeError。 当基础锁是RLock时,不会使用其 release() 方法,因为这可能不会在打开锁时实际解锁多次递归获取。而是一个内部接口使用 RLock 类的多次被递归获取。另一个内部接口是然后用于在重新获取锁时恢复递归级别。 |
6 | wait_for(predicate, timeout=None) | 等待直到条件变为 True。predicate 应该是一个可调用对象,且其结果为一个布尔值。timeout 用于给定最长的等待时长。 |
2.2 threading.Conditon 使用示范
下面的代码以生产者-消费者为例子,一个资源池最多可以存放 5 个物品,一次生产线程最多生成 5 个物品,消费者每次是取走一个物品,如果资源池中少于4个物品,则提醒生产线程继续生成物品。
请注意 Producer 中的等待代码 self.con.wait(),没有使用 timeout,这样它是会一直阻塞等待的。
而 Consumer 中的等待代码 self.con.wait(6),等到6秒后自动解除阻塞等待状态,请大家想想是为什么。
import threading
import time
max_goods_num=10
min_goods_num=4
con = threading.Condition()
num = 0
class Producer(threading.Thread):
def __init__(self, con):
self.con = con
super().__init__()
def run(self):
global num
print("*"*50)
print("Coming in Producer ",time.ctime())
self.con.acquire()
for _ in range(max_goods_num):
print("----------进入循环生成物品程序------------")
print("开始生成物品")
num += 1
print("资源池里面物品的个数为:{}".format(num))
time.sleep(1)
if num == 5:
print("资源池里面物品的个数已经到达五个, 无法继续生成了")
self.con.notify()
self.con.wait()
self.con.release()
print("Producer run exit")
class Consumer(threading.Thread):
def __init__(self, con):
self.con = con
super().__init__()
def run(self):
print("*"*50)
print("Coming in Consumer ",time.ctime())
self.con.acquire()
global num
while num:
print("----------进入循环消耗物品程序------------")
num -= 1
print("资源池里面物品剩余:{}".format(num))
time.sleep(0.5)
if num <min_goods_num :
print("资源池里面物品数量小于 min_goods_num,需要添加!")
self.con.notify()
self.con.wait(6)
self.con.release()
print("Consumer run exit")
p = Producer(con)
c = Consumer(con)
p.start()
c.start()
'''
要是大家觉得写得还行,麻烦点个赞或者收藏吧,想个博客涨涨人气,非常感谢!
'''