学科分类
目录

通过Condition类实现线程同步

Condition类代表条件变量,它允许线程在触发某些事件或达到特定条件后才开始执行。通过Condition类提供的构造方法可以创建一个实例,该方法的声明如下:

Condition(lock = None

以上构造方法中只有一个lock参数,该参数用于接收一个Lock对象或RLock对象。若没有为lock参数传入值,Condition对象会自动生成一个RLock对象。

Condition类中提供了与锁相关的acquire()和release()方法,这两个方法与Lock类中的用法一致,该类还提供了以下一些常用的方法:

  • wait(timeout=None):释放线程持有锁的同时挂起线程,直至线程接收到通知被唤醒或超时。

  • notify():唤醒一个处于挂起状态的线程。

  • notify_all() :唤醒所有处于挂起状态的线程

综上所述可知,Condition类中包含一个锁和一个等待池(放置处于阻塞状态的线程)。线程先调用acquire()方法对Condition对象的内部锁上锁,并判断其是否满足条件,具体分为以下两种情况:

(1) 若不满足条件,Condition对象调用wait()方法释放内部的锁,并将线程转换为阻塞状态,同时在等待池中记录处于阻塞状态的这个线程;

(2) 若满足条件,Condition对象调用notify()或notify_all() 方法唤醒等待池中的任意一条线程或所有线程,并将线程调用acquire()方法尝试将Condition对象的内部锁上锁。

假设某账户开户后余额为0,它具备支出和收入的统计功能。现在系统中分配了两个线程扮演“存款者”和“取款者”两个角色,分别执行向账户中存钱和取钱的行为:若当前账户的余额为0,先要求“取款者”处于等待状态,再要求“存款者”立即向账户中存钱;若当前账户中有余额,先要求“存款者”处于等待状态,再要求“取款者”立即从账户中取出所有的钱。“存款者”和“取款者”不允许连续两次存钱和取钱,并在执行完成后显示账户余额。下面使用程序来模拟以上场景,代码如下:

 1  import threading
 2  from threading import Thread, Condition
 3  class Account(object):
 4    def __init__(self, account_no, balance):
 5      self.account_no = account_no    # 账户编号
 6      self._balance = balance       # 账户余额
 7      self.cond = Condition()       # 创建表示条件变量的cond对象
 8      self._flag = False          # 标记是否已有存款
 9    # 取钱操作
 10   def draw_money(self, draw_amount):
 11     # 加锁,相当于调用Condition绑定的Lock的acquire()
 12     self.cond.acquire()
 13     try:
 14       # 如果self._flag为假,说明账户中还没有人存钱进去,取钱方法阻塞
 15       if not self._flag:
 16         self.cond.wait()
 17       else:
 18         # 执行取钱操作
 19         print(threading.current_thread().name + 
 20             " 取钱:" + str(draw_amount))
 21         self._balance -= draw_amount
 22         print("账户余额为: " + str(self._balance))
 23         # 将表示账户是否已有存款的标记设为False
 24         self._flag = False
 25         # 唤醒其他线程
 26         self.cond.notify_all()
 27     # 使用finally块来释放锁
 28     finally:
 29       self.cond.release()
 30   # 存钱操作
 31   def deposit(self, deposit_amount):
 32     # 加锁,相当于调用Condition绑定的Lock的acquire()
 33     self.cond.acquire()
 34     try:
 35       # 如果self._flag为真,说明账户中已有人存钱进去,存钱方法阻塞
 36       if self._flag:
 37         self.cond.wait()
 38       else:
 39         # 执行存款操作
 40         print(threading.current_thread().name + 
 41             " 存钱:" + str(deposit_amount))
 42         self._balance += deposit_amount
 43         print("账户余额为: " + str(self._balance))
 44         # 将表示账户是否已有存款的标记设为True
 45         self._flag = True
 46         # 唤醒其他线程
 47         self.cond.notify_all()
 48     # 使用finally块来释放锁
 49     finally:
 50       self.cond.release()
 51 # 创建一个账户
 52 acct = Account("123456" , 0)
 53 for i in range(5):
 54   # 创建、并启动一个“取钱”线程
 55   threading.Thread(name="取款者", target=acct.draw_money,
 56             args=(500,)).start()
 57   # 创建、并启动一个“存款”线程
 58   threading.Thread(name="存款者", target=acct.deposit,
 59             args=(500,)).start()

以上代码中第3~50行是定义的Account类,该类中共包含三个方法:__ init__()、draw_money()和deposit()方法,分别用于账户初始化、取钱和存钱。

其中__ init__()方法中定义了4个属性:account_no、balance、cond和 flag。其中,_ balance表示账户余额;cond表示创建的Condition对象,用于在满足条件的情况下阻塞和唤醒线程;_ flag表示账户是否已有存款的标记,True为已有存款,False为没有存款。

draw_money()方法中,先对cond的内部锁进行锁定,之后使用if-else语句区分账户有存款和无存款两种情况,若not _ flag为True,说明账户中没有存款,需要通过“cond.wait()”将处于阻塞的线程放入等待池中;若not _ flag为False,说明账户中有存款,将账户余额_ balance全部取出,_ flag的标记修改为False,并通过“cond.notify_all()”将所有处于等待池中的线程唤醒。

同样,deposit()方法中,先对cond的内部锁进行锁定,之后使用if-else语句区分账户有存款和无存款两种情况,若_ flag为True,说明账户中有存款,通过“cond.wait()”将处于阻塞的线程放入等待池中;若_ flag为False,说明账户中没有存款,将账户余额_ balance重置为存款,_flag的标记修改为True,并通过“cond.notify_all()”将所有处于等待池中的线程唤醒。

第52~59行创建了一个余额为0的账户acct,也创建了5次名称为“存款者”和“取款者”的线程,分别执行代表存钱行为的acct.deposit和代表取钱行为的draw_money。

运行代码,结果如下所示。

存款者 存钱:500
账户余额为: 500
取钱者 取钱:500
账户余额为: 0
存款者 存钱:500
账户余额为: 500
取钱者 取钱:500
账户余额为: 0
存款者 存钱:500
账户余额为: 500
取钱者 取钱:500
账户余额为: 0
存款者 存钱:500
账户余额为: 500
取钱者 取钱:500
账户余额为: 0
存款者 存钱:500
账户余额为: 500

由以上结果可知,因为账户的余额为0,所以先执行了账户存钱的行为,打印“存款者 存钱:500”,之后又执行了账户取钱的行为,打印“取钱者 取钱:500”...... 共打印了5次“存款者 存钱:500”和5次“取钱者 取钱:500”。由此表明,程序通过Condition对象成功实现了线程间的同步。

点击此处
隐藏目录