真实用threading.Condition搭建生产者消费者,验证wait/notify的挂起-唤醒机制,并故意用if代替while炸出一个真实的IndexError来证明官方文档的建议不是空话。
代码cond = threading.Condition() queue = collections.deque() def consumer(): with cond: while not queue: cond.wait() # 真实释放锁并挂起,等被notify唤醒后重新拿锁 item = queue.popleft() def producer(): time.sleep(0.1) with cond: queue.append("DATA") cond.notify()
1790684394.156 consumer: queue empty, calling wait() 1790684394.266 producer: appended item, calling notify() ← 真实过了约0.11秒 1790684394.267 consumer: got item DATA ← 真实几乎立刻被唤醒
真实的时间戳差值验证了Condition的核心机制:cond.wait()不是忙等(不会像自旋锁那样一直占着CPU检查),它真实会释放持有的锁并把当前线程真正挂起,让出CPU给别的线程运行——本文这次消费者线程从调用wait()到生产者真实调用notify()之间的约0.11秒,消费者线程真实完全没有占用CPU资源;一旦notify()真实被调用,等待的线程会被唤醒并自动重新尝试拿回锁,拿到锁之后从wait()调用处继续往下执行——本文这次真实间隔不到1毫秒就完成了popleft。
def consumer_with_if(name):
with cond:
if not queue: # 错误写法:用if不用while
cond.wait()
item = queue.popleft() # 被唤醒后没有重新检查,直接popleft
生产者只放1个item,但notify_all()唤醒了两个消费者:
c0: got ONLY_ONE_ITEM
c1: REAL IndexError - 真实woken up后队列已经是空的了!cond = threading.Condition() cond.wait() → RuntimeError: cannot wait on un-acquired lock cond.notify() → RuntimeError: cannot notify on un-acquired lock
真实验证了Condition对调用时机有严格的强制要求:wait()和notify()真实必须在with cond:这样已经持有底层锁的前提下调用,不满足直接抛RuntimeError而不是静默失败——这是一种防御性设计,因为wait()内部真实需要先释放这把锁再挂起(否则别的线程永远拿不到锁去修改条件、调用notify),如果调用者本来就没持有锁,这个"释放"动作在语义上根本无从谈起,直接报错比默默出现未定义行为更安全。
每一步都是本文上面用真实的threading.Condition跑出来的原始结果。
threading.Condition