虚假唤醒不是bug而是标准允许行为;必须用while循环或wait(lock, predicate)重验条件,因if只检查一次且不防状态被改,谓词需原子读取受同一锁保护的共享变量并避免UB。

虚假唤醒不是 bug,是标准允许的行为;不处理它,才是真正的 bug。
为什么 if + wait() 一定会出问题
用 if (!ready) 包裹 wait() 看似简洁,但只要发生一次虚假唤醒,线程就会跳过等待、直接执行后续逻辑——此时 ready 仍是 false,共享状态未就绪,极大概率触发数据竞争或崩溃。
-
if只检查一次,唤醒后绝不重验条件 - 即使系统只唤醒你一次,也可能在你抢到锁前,被其他线程改掉状态(比如队列又被清空)
- C++ 标准明确要求:必须循环验证谓词,否则行为未定义
wait(lock, predicate) 是最简 safest 写法
这个重载版本内部自动展开为 while 循环,语义清晰、无遗漏、不易写错。比手写 while 更推荐,尤其对新手。
- 谓词必须是可调用对象,且捕获方式要安全:
[&ready]{ return ready; }或[this]{ return data_ready_; } - 谓词里不能抛异常,否则
wait行为未定义 - 它和手写
while在性能上无差异——编译器会内联优化 - 错误示例:
cv.wait(lock, ready);——ready是bool,不是可调用对象,编译不过
谓词必须保护所有依赖的共享状态
虚假唤醒本身不可控,但你能控制“判断条件是否成立”这件事是否原子、是否受锁保护。谓词看似只是一行 lambda,实则定义了临界区的入口契约。
- 如果谓词读取多个变量(如
!queue_.empty() && !shutdown_),它们必须全部由同一把std::mutex保护 - 禁止在谓词外提前读取状态再传入,例如:
bool q_empty = queue_.empty(); cv.wait(lock, [&q_empty]{ return !q_empty; });——q_empty是快照,非实时,完全失效 - 生产者修改状态和通知必须严格配对:
lock→ 改状态 →notify_one()→unlock,漏掉任一环节都可能丢信号
最容易被忽略的点:谓词里访问的每个变量,都必须确保在 wait 持有锁期间始终有效——比如指向堆对象的裸指针,若对象在等待中被析构,谓词一执行就 UB。安全做法是用 shared_ptr 捕获,或确保生命周期覆盖整个等待周期。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











