必须用 while 循环包裹 wait(),因为虚假唤醒是 c++ 标准允许的行为,需每次唤醒后重新验证条件;正确写法为 while (!ready) { cv.wait(lock); } 或使用带谓词的 cv.wait(lock, pred),谓词须只读、纯检查且捕获所有依赖变量。

必须用 while 循环包裹 wait(),不能用 if
虚假唤醒是 C++ 标准明确允许的行为,不是 bug。线程可能在没收到 notify_one() 的情况下从 wait() 返回——比如内核调度抖动、信号中断或 futex 实现细节。用 if (!ready) { cv.wait(lock); } 会导致线程跳过检查直接执行后续逻辑,此时 ready 仍为 false,queue.front() 或 shared_ptr::get() 就会触发未定义行为。
正确做法是强制每次唤醒后都重新验证条件:
-
while (!ready) { cv.wait(lock); }—— 每次返回都重读ready,确保它真为true - 循环体里只调
wait(),不要加break、return或其他状态修改操作 - 别写成
while (ready),逻辑反转会让线程永不等待 - 别在
wait()前手动调lock.unlock(),破坏原子性
cv.wait(lock, predicate) 是更安全的默认选择
这个重载内部就是帮你展开成等价的 while (!pred()) { cv.wait(lock); },语义清晰、不易漏写循环,且编译器能做更多检查。但谓词本身必须严格满足要求:
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- 捕获方式推荐
[&],避免漏掉某个依赖变量(比如队列和关闭标志都要一起捕获) - 谓词必须是纯检查:只读共享变量,不修改、不抛异常、不打日志、不调
pop()或++counter - 所有被访问的变量(如
task_queue、shutdown_flag)必须由同一std::mutex保护 - 错误示例:
[&]{ task_queue.pop(); return true; }—— 谓词会被多次调用,导致重复消费
wait_for() 和 wait_until() 也必须带谓词
超时不是逃避虚假唤醒的理由。不带谓词的 wait_for() 返回 std::cv_status::no_timeout 只表示“没超时”,不代表条件成立。线程醒来后依然可能面对空队列或未就绪状态。
- 正确写法:
cv.wait_for(lock, 200ms, []{ return !queue.empty(); }) - 返回
true表示谓词为真;返回false表示超时且谓词仍为假 - 别写成:
if (cv.wait_for(lock, 100ms) == std::cv_status::no_timeout) { /* assume ready */ }—— 这等于放弃防御 - 如果业务允许降级,
false分支应明确处理:重试、记录警告或退出
通知端必须“锁内改状态 + 锁内 notify”
虚假唤醒本身不可控,但“唤醒后看到错误状态”往往源于通知端竞态。如果 notify_one() 在状态更新前发出,或在锁释放后才调用,等待线程可能永远阻塞,或抢到锁时看到的是旧状态。
- 正确顺序:
std::unique_lock<:mutex> lock(mtx); data = 42; cv.notify_one();</:mutex>(全部在锁内) - 错误顺序:
data = 42; lock.unlock(); cv.notify_one();—— 中间存在竞态窗口 - 复合条件(如
!queue.empty() && !shutdown_flag)必须在同一个谓词里检查,不能拆开判断 - 即使用了谓词,也要确保谓词检查通过后,紧接着的操作(如
pop())仍在同一锁保护下完成
真正容易被忽略的点不在“怎么写谓词”,而在于:谓词检查通过、锁还没释放、你刚准备读数据那一瞬间——别的线程可能已经把它消费掉了。所以“检查 + 操作”必须原子地落在同一临界区内,谓词只是第一道门,不是最后一道。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










