必须用 while 循环检查谓词,因为虚假唤醒是标准允许的行为,线程醒来后必须重新验证条件;谓词需无副作用、覆盖所有相关变量,且状态更新与 notify 必须在同锁内完成。

必须用 while 循环检查谓词,不能用 if;否则线程可能在条件未满足时直接执行后续逻辑,引发数据竞争或崩溃。
为什么 std::condition_variable::wait 一定要配 while 而不是 if
虚假唤醒是标准允许的行为,不是 bug。它可能由信号中断、内核调度抖动、futex 唤醒策略等触发,线程醒来时根本无法区分是真通知还是假唤醒。用 if (!ready) cv.wait(lock) 的话,一旦发生虚假唤醒,线程就跳过检查直接往下走,此时 ready 还是 false,后续读写极易出错。
正确做法是让每次唤醒后都重新验证条件:
-
while (!ready)是必须的,不是可选项 - 谓词必须是无副作用的纯检查(例如
queue.empty()可以,queue.pop()绝对不行) - 如果条件涉及多个变量(如
!queue.empty() && !shutdown_flag),必须全部放在同一个谓词里检查,不能拆开
cv.wait(lock, predicate) 真的能省心吗
这个重载内部就是帮你写了 while (!pred()) wait(lock),语义等价,但不解决底层原子性与可见性问题。
常见翻车点:
- 谓词里访问的变量没被同一
std::mutex保护(比如queue有锁,但shutdown_flag是std::atomic<bool></bool>却没在谓词中一并判断) - 谓词返回
true后,还没进入临界区,其他线程已改掉状态(典型竞态),所以仍要靠“锁 + 检查 + 操作”三步原子组合 - 误以为用了谓词就万事大吉,结果把退出条件分散在多处,漏掉关键判断
生产者-消费者模型中最容易漏掉的两个检查点
真实场景下,仅检查 !queue.empty() 是不够的。线程退出逻辑往往依赖复合条件:
- 队列非空 且 未收到关闭信号(
!queue.empty() && !shutdown_requested) - 通知发出前,必须确保状态更新和
notify_one()在同一锁作用域内完成,否则可能丢失通知 - 若使用
wait_for或wait_until,需检查返回值:返回false表示超时或虚假唤醒,不能直接当作条件成立处理
锁作用域和通知时机的细节决定成败
虚假唤醒本身不可控,但“唤醒后看到错误状态”是可以预防的。核心在于让状态变更与通知严格同步:
- 修改共享变量(如
ready = true)前,必须已持有对应std::mutex -
cv.notify_one()必须在同一个锁保护块内调用,不能在释放锁之后 - 避免把
notify放在 if 分支外——比如先判断if (queue.size() > threshold)再 notify,可能导致通知早于状态写入
最易被忽略的是:谓词检查通过后,进入业务逻辑前的那一小段窗口期,仍是竞态高发区。哪怕用了 wait(lock, pred),只要操作本身不原子,就得靠锁延续保护。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











