Python3之并发(五)---线程条件(Condition)和事件(Event)
一、线程条件Condition(条件变量)
依赖锁对象(Lock,RLock),锁对象可以通过参数传入获得,或者使用自动创建的默认锁对象
当多个条件变量需要共享同一个锁时,建议传入锁对象
除了带有获取到锁的锁定池,Condition还包含一个未获取到锁的等待池,等待池中的线程处于等待阻塞状态,直到锁定池中的线程调用notify()/notifyAll()通知,得到通知后线程进入锁定池等待锁定
threading.Condition(lock=None)
条件变量对象,允许一个或多个线程等待,直到被另一个线程通知为止
lock: 使用的锁对象,默认None,使用RLock锁对象,其他值使用RLock或Lock锁对象
Condition对象的方法
Condition.acquire(*args) 加锁 获取锁,获取成功返回 True,否则返回 False Condition.release() 释放锁,没有返回值 Condition.wait(timeout=None) 等待通知的最大超时时间,线程进入等待池并释放锁,直到锁定池中的其他线程调用notify()/notifyAll()通知唤醒该线程 timeout时间内获取到通知返回 True,否则返回 False timeut: 超时时间,浮点数,默认None 当没有获去到锁的线程调用该方法引发 RuntimeError 异常 Condition.notify(n=1) 唤醒等待池中的线程(默认n=1),收到通知的线程将自动调用acquire()方法尝试加锁,若等待池中有多个线程,则随机选择当中任意一个线程 Condition.notify_all() 唤醒等待池中的所有线程
示例
食物添加和消费
import threading class Food(): def __init__(self, Max): #初始食物个数和要加入食物的最大个数 self.num = 0 self.max = Max ''' 是否加入食物的标志 self._flag=True,表示需要加入食物,消费食物方法被堵塞 self._flag=False,表示消费食物,加入食物方法被堵塞 ''' self._flag = True self.cond = threading.Condition() #加入食物方法 def food_producer(self): with self.cond: if self._flag: for i in range(self.max): self.num += 1 print(threading.current_thread().name+'线程已添加食物个数:',str(self.num),', 总共',str(self.num),'个') print('当前食物个数:'+str(self.max),'\t请消费食物') self._flag = False self.cond.notify_all() else: self.cond.wait() #消费食物方法 def food_consumer(self): with self.cond: if self._flag: self.cond.wait() else: for i in range(self.max): self.num -= 1 print(threading.current_thread().name+'线程已消费食物个数:',str(i+1),',当前还剩食物个数:',str(self.num)) print('当前食物个数:'+str(self.num)+'\t食物已消费完,请添加食物') self._flag = True self.cond.notify_all() foo = Food(20) threading.Thread(target=foo.food_producer, name='food_producer_thread').start() threading.Thread(target=foo.food_consumer, name='food_consumer_thread').start()
存取钱
import threading #账户类 class Account: def __init__(self, account_no, balance): #账户编号和账户余额 self.account_no = account_no self.balance = balance ''' 存取钱标志 self._flag=True表示取钱,存钱方法被堵塞 self._flag=False表示存钱,取钱方法被堵塞 ''' self._flag = False self.cond = threading.Condition() def getBlance(self): return self.balance #提取现金方法 def draw(self, draw_amount): with self.cond: if not self._flag: self.cond.wait() else: if self.balance >= draw_amount : print(threading.current_thread().name+'\t取钱成功!吐出钞票:'+str(draw_amount)) self.balance -= draw_amount print(threading.current_thread().name+'操作之后\t余额为:'+str(self.balance)) else: print(threading.current_thread().name+'\t取钱失败!余额不足!\t当前余额为:'+str(self.balance)) self._flag = False self.cond.notify_all() #存钱方法 def deposit(self,deposit_amount): with self.cond: if self._flag: self.cond.wait() else: print(threading.current_thread().name+'\t存钱成功!存入钞票:'+str(deposit_amount)) self.balance += deposit_amount print(threading.current_thread().name+'操作之后\t余额为:'+str(self.balance)) self._flag = True self.cond.notify_all() acct = Account('987456',0) for i in range(5): thread1 = threading.Thread(name="acct_deposit_thread", target=acct.deposit, args=(800,)) thread1.start() thread2 = threading.Thread(name="acct_draw_thread", target=acct.draw, args=(900,)) thread2.start()
二、线程事件Event
Event是最简单的线程通信机制之一: 一个线程发出事件信号,其他线程等待接收该信号
不支持with 上下文
threading.Event 相当于Condition+用户自定义的Flag Event管理一个内部标识(Flag),默认值是 False,调用 set() 方法可将Flag设置为 True ,也可以调用 clear() 方法可将Flag设置为False 当Flag=False时,调用 wait() 方法将进入阻塞状态,直到Flag为 true
Event对象的方法
Event.is_set() Event内部标识为True返回 True Event.set() 将Event内部标识设为 True ,所有正在等待这个事件的线程将被唤醒 当标识为 True 时,调用 wait() 方法的线程不会被被阻塞 Event.clear() 将Event内部标识设为 False ,之后调用 wait() 方法的线程将会被阻塞,直到调用 set() 方法将内部标识再次设置为 True Event.wait(timeout=None) 线程阻塞的最大超时时间,此时间内线程一直被堵塞,直到内部标致为 True timeout: 超时时间,默认None,浮点数,单位秒 调用该方法之前先释放锁,即调用release()释放锁,否则容易产生死锁
示例
食物添加和消费
import threading class Food(): def __init__(self, Max): #初始食物个数和要加入食物的最大个数 self.num = 0 self.max = Max self.lock = threading.RLock() self.event = threading.Event() #加入食物方法 def food_producer(self): self.lock.acquire() try: if not self.event.is_set(): for i in range(self.max): self.num += 1 print(threading.current_thread().name+'已添加食物个数:',str(self.num)+',当前总共',str(self.num),'个') print('当前食物个数:'+str(self.max),'\t请先消费食物') self.event.set() self.lock.release() else: self.lock.release() self.event.wait() finally: pass #消费食物方法 def food_consumer(self): self.lock.acquire() try: if self.event.is_set(): for i in range(self.max): self.num -= 1 print(threading.current_thread().name+'已消费食物个数:',str(i+1),'个食物,当前还剩余食物个数:',str(self.num)) print('当前食物个数:'+str(self.num)+'\t食物已消费完,请添加食物') self.event.clear() self.lock.release() else: print(threading.current_thread().name+'的内置标识为{}'.format(self.event.is_set())) self.lock.release() self.event.wait() finally: pass foo = Food(20) for i in range(2): thread2 = threading.Thread(target=foo.food_consumer, name='消费者') thread2.start() thread1 = threading.Thread(target=foo.food_producer, name='生产者') thread1.start()
存取钱
import threading #账户类 class Account: def __init__(self,account_no,balance): #账户编号和账户余额 self.account_no = account_no self.balance = balance self.lock = threading.RLock() self.event = threading.Event() def getBlance(self): return self.balance #提取现金方法 def draw(self,draw_amount): self.lock.acquire() try: if not self.event.is_set(): print(threading.current_thread().name+'的内置标识为{}'.format(self.event.is_set())) self.lock.release() self.event.wait() else: if self.balance >= draw_amount: print(threading.current_thread().name+'\t取钱成功!吐出钞票:'+str(draw_amount)) self.balance -= draw_amount print(threading.current_thread().name+'操作之后\t余额为:'+str(self.balance)) else: print(threading.current_thread().name+'\t取钱失败!余额不足!\t当前余额为:'+str(self.balance)) self.event.clear() self.lock.release() finally: pass def deposit(self,deposit_amount): self.lock.acquire() try: if self.event.is_set(): self.lock.release() self.event.wait() else: print(threading.current_thread().name+'\t存钱成功!存入钞票:'+str(deposit_amount)) self.balance += deposit_amount print(threading.current_thread().name+'操作之后\t余额为:'+str(self.balance)) self.event.set() self.lock.release() finally: pass acct = Account('987456', 0) #循环取存操作5次, for i in range(5): thread2 = threading.Thread(name="取钱者", target=acct.draw, args=(700,)) thread2.start() thread1 = threading.Thread(name="存钱者", target=acct.deposit, args=(800,)) thread1.start()