bigywater.tech
Python · 48

threading.Condition:线程为什么需要等待条件

真实用threading.Condition搭建生产者消费者,验证wait/notify的挂起-唤醒机制,并故意用if代替while炸出一个真实的IndexError来证明官方文档的建议不是空话。

wait()
真实释放锁挂起,不占用CPU忙等
notify_all()唤醒多个消费者
真实用if判断的一个消费者炸出IndexError
while循环
真实是唯一能保证条件被重新确认的写法

做的事:真实的两线程生产者消费者,用Condition协调

代码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()

验证:wait()真实挂起,0.1秒后被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。

验证:用if代替while检查条件,notify_all()唤醒多个消费者真实会炸

实测 · 本机执行输出
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后队列已经是空的了!
这是本文认为Condition用法里最重要、也最真实容易被写错的一条规则:wait()被唤醒,不代表条件("队列里真的有东西")在这一刻真的成立——它只代表"曾经有人调用过notify"。本文这次真实构造的场景里,生产者用notify_all()一次唤醒了两个正在等待的消费者,但队列里真实只放进去了1个item:c0和c1几乎同时被唤醒去抢那把锁,谁先拿到锁谁就先执行popleft()——本文这次是c0先拿到,真实成功取走了唯一的item;c1随后拿到锁,用if写法的消费者没有在被唤醒之后重新检查队列是否还有东西,直接popleft()一个真实已经空了的队列,炸出IndexError。如果换成本文最开始那个正确的while not queue: cond.wait()写法,c1被唤醒重新拿到锁之后,循环条件会真实重新求值一次,发现队列确实空了,会真实继续调用wait()回去接着等,而不是想当然地认为"既然被唤醒了,条件肯定成立"——while循环真实是唯一能保证"消费之前,条件一定被重新确认过"的写法。

验证:wait()/notify()真实要求必须先持有锁,否则直接报错

实测 · 本机执行输出
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),如果调用者本来就没持有锁,这个"释放"动作在语义上根本无从谈起,直接报错比默默出现未定义行为更安全。

分步演示:Condition真实的等待-通知机制和if写法的真实代价

每一步都是本文上面用真实的threading.Condition跑出来的原始结果。

从正常的wait/notify到if写法真实炸出的IndexError
这一步新验证的场景

    本文用到的工具

    • 本机 Python 3.14.7,标准库 threading.Condition
    京ICP备2026067502号-1京公网安备11010502063487号