必须用 while 循环包裹 wait() 或传谓词给 cv.wait(lock, predicate),因虚假唤醒是标准允许行为,if 会导致条件未满足就继续执行;谓词须无副作用且所有共享变量由同一 mutex 保护。

必须用 while 循环包裹 wait(),或直接传谓词给 cv.wait(lock, predicate)——否则线程大概率在条件未满足时就执行后续逻辑,读空队列、解空指针、触发未定义行为。
为什么不能用 if 包裹 wait()
虚假唤醒是 C++ 标准明确允许的行为,不是 bug。线程可能因信号中断、futex 唤醒抖动、缓存同步等原因,在没收到 notify_one() 的情况下就从 wait() 返回。此时若只写 if (!ready) { cv.wait(lock); },线程会跳过检查直接往下走,而 ready 仍是 false。
- 典型错误现象:
queue.front()在空队列上调用,崩溃;shared_ptr解引用空指针;状态机进入非法分支 -
wait()返回 ≠ 条件成立,它只表示“可以重新检查”,必须验证 - 即使本地压测不复现,高并发、多核、低延迟场景下极易暴露
用 cv.wait(lock, predicate) 最简且安全
这个重载内部就是帮你展开成 while (!pred()) { cv.wait(lock); },语义清晰、无遗漏风险,推荐作为默认选择。
- 谓词必须是无副作用的纯检查表达式,例如:
[&]{ return !task_queue.empty(); } - 所有被访问的共享变量(如
task_queue、shutdown_flag)必须由同一std::mutex保护 - 捕获方式推荐
[&],避免漏掉某个依赖变量导致编译失败或未定义行为 - 别在谓词里做
queue.pop()、counter++、log()等操作——它会被多次调用
手写 while 循环时最容易踩的坑
显式循环适合需要加日志、计数、或兼容旧接口的场景,但容错性更低,稍不注意就出错。
立即学习“C++免费学习笔记(深入)”;
- 写成
while (ready)而非while (!ready):逻辑反转,线程永不等待 - 在
wait()前手动lock.unlock():破坏原子性,造成竞态窗口 - 用
std::lock_guard替代std::unique_lock:后者支持临时释放锁,前者不支持wait() - 谓词中读取未加锁保护的变量(如把
atomic<bool></bool>和非原子变量混用却不在同一谓词里判断)
wait_for() 和 wait_until() 也要带谓词
超时不是逃避虚假唤醒的理由。不带谓词的 wait_for() 返回 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表示超时且谓词仍为false;返回true才表示谓词已满足 - 如果业务需区分“超时”和“虚假唤醒”,只能靠带谓词版本 + 外层状态记录
最易被忽略的是:谓词返回 true 后、你真正开始操作前那一小段窗口期——其他线程仍可能改掉状态。所以“锁 + 检查 + 操作”三步必须在同一个临界区内完成,不能拆开。


















