必须用while循环检查谓词而非if,因wait返回仅表示被唤醒而非条件成立,需防范虚假唤醒和条件被篡改;谓词应原子化;修改共享状态后须notify;condition_variable必须与同一mutex配对使用。

条件变量等待前必须用 while 循环检查谓词
直接用 if 判断条件变量的触发条件,是 C++ 多线程中最隐蔽也最常出问题的操作。哪怕你在锁区内修改了共享状态,notify_one() 唤醒了等待线程,它一醒来就执行后续逻辑——但此时条件可能早已再次失效(比如被另一个线程抢走资源),或者压根没真正满足(虚假唤醒)。
正确做法永远是用 while 循环包裹 wait():
std::unique_lock<std::mutex> lock(mtx);
while (!data_ready) {
cond_var.wait(lock);
}
// 此时 data_ready 一定为 true,且仍持有锁
-
wait()返回不等于“条件成立”,只代表被唤醒,必须重新检查谓词 - 使用
while能同时覆盖虚假唤醒(spurious wakeup)和条件被中途篡改两种情况 - 不要把谓词拆成多个变量判断(如
if (queue.size() > 0 && !shutdown)),应封装为单一原子布尔状态,或用wait()的带谓词重载:cond_var.wait(lock, []{ return !queue.empty() && !shutdown; });
锁区内修改条件后必须调用 notify_one() 或 notify_all()
修改了影响等待线程判断的共享变量(比如 data_ready = true、queue.push(item)),却不通知,等待线程就会永远卡在 wait() —— 即使锁已释放,内核不会自动感知用户态变量变化。
常见错误场景:
立即学习“C++免费学习笔记(深入)”;
- 在
if分支里修改状态并notify,但漏掉了else if或其他路径 - 用
notify_all()过度唤醒:当只有一个消费者能处理任务时,用notify_one()更高效;若多个线程需响应同一事件(如广播 shutdown),才用notify_all() -
notify_*调用位置无关是否持锁:它只是向内核发信号,可在锁内或锁外调用;但习惯上放在锁内更安全(避免 notify 后立即修改状态导致竞争)
条件变量 + 互斥量必须严格配对使用
std::condition_variable 不维护任何状态,也不绑定任何锁;它完全依赖用户保证:所有对谓词的读写都发生在同一把 std::mutex 保护下,且 wait() 必须传入该锁对应的 std::unique_lock。
典型误用:
- 用
std::lock_guard而非std::unique_lock调用wait()→ 编译失败,因为wait()需要能临时释放并重获锁的能力 - 一个条件变量搭配多把不同 mutex 使用 → 行为未定义,可能死锁或静默失败
- 谓词读取未加锁(比如在
wait()外裸读data_ready)→ 数据竞争,data_ready可能被优化掉或读到撕裂值
避免在 wait() 前做耗时操作或阻塞调用
wait() 之前如果做了网络请求、磁盘读写或复杂计算,会延长临界区时间,降低并发度,还可能让其它线程长时间等不到锁。
更危险的是:这些操作可能抛异常,而你又没在 try/catch 中保护整个锁区,导致锁提前释放、状态不一致。
- 把耗时操作移到锁外;只在锁内做最小必要状态更新和
notify - 若必须判断后再决定是否等待(例如先查缓存命中再决定 wait),把判断逻辑也尽量精简,并确保判断依据本身是锁保护的
- 注意
wait_for()和wait_until()的超时返回值是std::cv_status,不是布尔值;别错当成“true=成功”来用
最易被忽略的一点:条件变量没有“消息队列”语义,notify 不会排队,也不会累积。一次 notify 最多唤醒一个等待者,且仅对当时正在 wait 的线程有效;如果 notify 发生在 wait 之前,这次通知就彻底丢失。


















