生产者/消费者模型:Consumer类是否需要调用condition.release()?
condition.release()不能省略? 你在测试这段生产者消费者代码时,发现注释掉Consumer中的self.condition.release()后程序好像能正常运行,这确实容易让人疑惑——难道这段代码是多余的?其实不是,它不仅必要,而且是保证线程同步逻辑正确、避免潜在问题的关键。
1. 先搞懂Condition.wait()的底层行为
你可能以为wait()只是让线程等待,但它其实做了两件关键的事:
- 调用
wait()时,线程必须已经持有Condition的锁,wait()会自动完全释放这个锁(不管之前acquire了多少次),让其他线程(比如Producer)能拿到锁进行操作。 - 当被
notify()唤醒后,wait()会重新获取锁,之后才会继续执行后续代码。
那为什么你注释掉release()后程序还能跑?大概率是测试时Consumer每次都进入了wait()——比如Producer启动速度稍慢,Consumer拿到锁后发现列表为空,就调用wait()释放了锁,Producer才能顺利添加元素、调用notify(),Consumer被唤醒后重新拿到锁。这种场景下,wait()帮你“临时”处理了锁的释放,但这只是巧合,不是正确的用法。
2. 不写release()会埋下哪些坑?
场景一:锁计数膨胀,导致死锁
Condition默认使用RLock(可重入锁),同一线程可以多次调用acquire()而不阻塞。如果Consumer某次拿到锁后,列表刚好有元素,直接pop后没调用wait(),也没release(),就进入下一次外层循环再次acquire()——这时候锁的持有计数会从1变成2。
之后如果再遇到列表为空调用wait(),wait()会释放锁到0,唤醒后又恢复到2。长此以往,锁的计数会越来越高,一旦Producer结束运行,Consumer会因为没有新的notify()而永远阻塞在wait()里,而且这个锁的计数永远无法归0,属于隐性的资源泄漏。
场景二:破坏锁的使用规范
锁的核心原则是**acquire()和release()必须配对**,这是线程同步的基本规范。即使RLock允许重入,也不能依赖这个特性来省略release()——这会让代码逻辑变得混乱,其他开发者接手时很容易误解锁的作用范围,甚至引入新的线程安全问题。
3. 正确的代码写法
必须把self.condition.release()从注释里放出来,放在内部while循环结束后,和最开始的acquire()配对:
class Consumer(threading.Thread): def __init__(self, condition, variables): threading.Thread.__init__(self) self.condition = condition self.variables = variables def run(self): while True: self.condition.acquire() print("condition was acquired by {}".format(self.name)) while True: if self.variables: number = self.variables.pop() print("'{}', was popped from list by {}".format(number, self.name)) break print("condition is waited by {}".format(self.name)) self.condition.wait() # 必须加上这行,和开头的acquire()配对 self.condition.release() print("condition was released by {}".format(self.name))
这样每次Consumer处理完一个元素后,都会正确释放锁,让Producer或其他Consumer(如果有的话)能拿到锁,保证整个同步逻辑的正确性。
内容的提问来源于stack exchange,提问作者dildeolupbiten

