std::condition_variable::wait()必须配合循环检查谓词或使用带谓词的重载版本,因虚假唤醒是标准允许的行为,wait返回不保证条件成立,需重新验证;谓词须无副作用且访问受同一mutex保护。

直接说结论:std::condition_variable::wait() 必须配合循环检查谓词,或直接使用带谓词的重载版本——否则一定出问题。
为什么不能用 if 包裹 wait()
虚假唤醒不是 bug,是 POSIX 和 C++ 标准明确允许的行为。线程可能因信号中断、缓存同步、调度器优化等原因,在没收到 notify_one() 或 notify_all() 的情况下就返回 wait()。此时若只用 if (!ready) { cv.wait(lock); },线程会跳过等待,直接执行后续逻辑,而 ready 仍是 false。
- 典型错误现象:消费者线程“醒来”后访问空队列,触发
std::queue::front()未定义行为 - 根本原因:
wait()返回 ≠ 条件成立,它只表示“可能成立”,必须重新验证 - 哪怕在 Linux x86_64 上复现率低,也不能假设它不会发生——生产环境压力下极易暴露
wait(lock, predicate) 是最简安全写法
这个重载内部自动实现循环检查,语义清晰、代码简洁、无遗漏风险。它等价于手写 while (!predicate()) { wait(lock); },但更可靠。
- 谓词必须是无副作用的纯函数(如 lambda
[] { return !data_queue.empty(); }) - 谓词中访问的共享变量(如
data_queue、ready)必须由同一std::mutex保护 - 不要在谓词里调用可能阻塞或抛异常的函数,否则
wait()行为不可控 - 示例:
cv.wait(lock, []{ return data_ready; });—— 安全、可读、标准推荐
手写 while 循环时容易踩的坑
有人坚持手写循环,认为更可控。但实际中常见三类错误:
- 忘记在循环条件里加
!:写成while (ready)而非while (!ready),导致永远不等 - 把条件判断和
wait()拆到不同锁作用域:比如先lock.unlock()再wait(),破坏原子性 - 谓词访问未受保护的变量:例如在
while (!queue.size())中直接读queue,而它没被互斥锁覆盖,引发数据竞争 - 误用
std::lock_guard:必须用std::unique_lock,因为wait()需要能临时释放锁
超时等待也要守同样规则
用 wait_for() 或 wait_until() 并不豁免虚假唤醒检查。它们返回 std::cv_status::no_timeout 只代表“被通知或超时”,不代表条件成立。
- 错误写法:
if (cv.wait_for(lock, 100ms) == std::cv_status::no_timeout) { /* assume ready */ } - 正确写法:
if (cv.wait_for(lock, 100ms, []{ return ready; }) == false) { /* 超时 */ },或继续用while+wait_for - 注意:超时版本的谓词重载返回
bool,true表示条件满足并退出等待,false表示超时
真正容易被忽略的点是:条件变量本身不保存状态,notify 不会“排队”。如果通知早于等待,且没有配套的标志位(如 bool ready),信号就丢了——这和虚假唤醒无关,但常被混为一谈。所以标志位 + 循环检查 + 正确加锁,三者缺一不可。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











