必须用循环检查谓词或传lambda给cv.wait(),因虚假唤醒会导致if+wait跳过条件重检,引发未定义行为;while循环显式可控,wait+谓词更简洁安全,但谓词须无副作用且捕获所有依赖变量。

必须用循环检查谓词,或者直接传 lambda 给 cv.wait() —— 这不是可选项,是 C++ 标准强制要求的防御措施。
为什么不能用 if + wait()
看似逻辑通顺的写法:if (!ready) { cv.wait(lock); },会在虚假唤醒发生时直接跳过条件重检,导致线程误认为条件已满足。此时 ready 仍为 false,后续访问可能引发未定义行为(如空指针解引用、队列越界弹出)。
常见错误现象包括:
- 消费者线程从空队列调用
front()或pop(),触发崩溃 - 状态标志未置位却执行初始化逻辑,造成资源重复构造
- 多核环境下偶发失败,本地难以复现
while 循环 vs wait(lock, predicate) 怎么选
两者都安全,但适用场景和可维护性不同:
立即学习“C++免费学习笔记(深入)”;
-
while (!ready) { cv.wait(lock); }:适合简单布尔状态,逻辑显式,调试时容易加断点观察循环次数 -
cv.wait(lock, []{ return !data_queue.empty(); });:推荐用于容器判空等表达式,内部自动展开为等效 while 循环,代码更紧凑,且避免手写循环时漏掉lock作用域控制 - 注意:谓词必须捕获所有依赖变量(如用
[&]捕获data_queue),否则编译不过或行为未定义
生产者通知前必须持锁修改状态
虚假唤醒本身不可控,但“丢失通知”(notify 发生在 wait 之前)和“状态撕裂”(修改 ready 时不加锁)会放大问题影响。正确顺序是:
- 生产者先获取
mtx,再修改共享变量(如data_queue.push(x)、ready = true) - 然后调用
cv.notify_one()或cv.notify_all() - 不能把
notify放在锁外——这会导致等待线程被唤醒后抢到锁时,状态还没更新完毕
容易被忽略的细节:谓词里别做副作用
cv.wait(lock, []{ return process_and_check(); }) 是危险的。谓词可能被多次调用(每次虚假唤醒都触发一次),如果里面包含日志、计数器自增、或修改其他状态,会导致逻辑错乱或竞态。
真正该放进谓词的,只有纯粹的、无副作用的状态检查表达式,例如:
[] { return !q.empty(); }[&] { return counter > threshold; }[this] { return state_ == State::DONE; }
复杂判断请拆到循环体外,只把最终布尔结果交给谓词。


















