必须使用带谓词的wait(),因虚假唤醒在Linux和Windows上真实存在,返回后不检查条件会导致读未就绪数据、空队列访问或未定义行为;谓词须为纯函数且受同一mutex保护,notify必须在状态更新之后、锁释放之前(或确保消费者已等待)调用,超时等待也必须配谓词。

必须用带谓词的 wait(),否则虚假唤醒几乎必然导致逻辑错误。
为什么 wait(lock) 不能单独用
无谓词的 wait() 返回后不检查条件是否真满足,而虚假唤醒在 Linux 和 Windows 上都真实存在——哪怕没人调 notify_one(),线程也可能被内核随机唤醒。此时若直接执行后续逻辑,就会读到未就绪的数据、访问空队列,甚至触发未定义行为。
常见错误现象:
- 消费者线程从
wait()返回后立刻pop(),却抛出std::out_of_range - 主线程以为子线程已初始化完成,实际还在等待,导致后续操作访问未初始化对象
正确做法是始终让条件判断和等待绑定在一起:
立即学习“C++免费学习笔记(深入)”;
std::unique_lock<std::mutex> lk(mtx);
cv.wait(lk, []{ return !queue.empty(); }); // 自动循环检查,安全
wait(lock, predicate) 怎么写才算安全
谓词必须是纯函数:只读取受同一 std::mutex 保护的共享状态,不修改、不引入外部依赖(如全局时钟、文件句柄)。
使用场景中容易踩的坑:
- 谓词里访问了没加锁的变量 → 数据竞争,行为未定义
- 谓词返回
true后,实际数据已被其他线程消费 → 必须在谓词内检查「当前仍有效」的状态,比如!queue.empty() && queue.front() > 0 - 把
ready声明为volatile bool→ 没用,volatile不提供同步语义,仍需std::mutex保护
推荐写法:
cv.wait(lk, [&]{ return status == READY && data_valid; });
notify 调用时机影响谓词是否来得及生效
即使用了谓词,如果 notify_one() 在修改共享状态前就调用,信号会丢失,等待线程永远阻塞——这不是虚假唤醒,而是「通知丢失」。
关键原则:修改状态和发通知必须在同一个锁保护下完成,或至少确保「状态更新先于 notify」。
典型错误顺序:
// ❌ 危险:unlock 后 notify,中间可能被抢占 std::unique_lock<std::mutex> lk(mtx); data = 42; lk.unlock(); // ← 此刻消费者可能刚进入 wait,但还没挂起 cv.notify_one(); // ← 信号发出,但无人接收
正确顺序:
// ✅ 安全:状态更新 + notify 都在锁内,或 notify 在 unlock 之后但确保消费者已等待
{
std::unique_lock<std::mutex> lk(mtx);
data = 42;
ready = true;
} // 自动 unlock
cv.notify_one(); // 通知发生在状态已更新之后
超时等待也要带谓词,别信 cv_status
wait_for() 和 wait_until() 的返回值是 std::cv_status,但仅靠它判断是否“真唤醒”不可靠:超时返回 cv_status::timeout,可唤醒后谓词仍为 false(比如生产者还没来得及 push)。
所以带超时的版本也必须配谓词:
auto result = cv.wait_for(lk, 100ms, [&]{ return !queue.empty(); });
if (!result) {
// 超时且条件仍不满足
return;
}
// 此时 queue 确实非空,可安全 pop
漏掉谓词会导致超时后仍尝试访问空容器,崩溃风险极高。
最常被忽略的一点:谓词里的变量必须是锁保护下的最新值;哪怕只差一行代码没加锁,整个等待逻辑就失效。


















