std::condition_variable必须与std::mutex配合使用,所有共享数据访问及wait/notify操作均需在mutex保护下进行;需用while循环检查条件以应对虚假唤醒;notify_one适用于单消费者场景,notify_all适用于批量生产或多消费者;退出时须设置原子标志并notify_all确保线程安全终止。

std::condition_variable 必须和 std::mutex 一起用
单独声明 std::condition_variable 没有意义,它不保存状态,也不提供互斥保护。所有对共享数据的读写、以及 wait() / notify_one() 的调用,都必须在同一个 std::mutex 的保护下进行。
常见错误是只用 mutex 保护数据修改,却在 unlock 后才调用 notify_one() —— 这会导致唤醒丢失:消费者刚 wait 完、还没来得及重新加锁,生产者就 notify 了,信号被丢弃。
- 正确做法:在持有 mutex 时修改完共享状态(如 push 到队列),**立刻**调用
notify_one()或notify_all(),再 unlock -
wait()内部会自动 unlock mutex,并在被唤醒后重新 lock;但唤醒后仍需检查条件是否真正满足(用 while 循环,不是 if) - 不要依赖“通知次数”:
notify_one()只唤醒一个等待线程,但无法保证是哪个;多个消费者竞争时,要用循环重检条件
wait() 必须配合谓词(predicate)使用
wait() 的裸调用(无 lambda)极易出错,因为 spurious wakeup(虚假唤醒)在 POSIX 和 C++ 标准中都是允许的。哪怕没人 notify,线程也可能被唤醒。
所以不能写 cv.wait(lock) 然后直接消费,而必须用 while 循环检查条件是否成立:
立即学习“C++免费学习笔记(深入)”;
while (queue.empty()) {
cv.wait(lock);
}
更推荐用带谓词的重载,语义清晰且自动处理循环:
cv.wait(lock, [&] { return !queue.empty(); });
- 谓词返回
false→ 继续 wait 并自动 unlock/relock - 谓词返回
true→ 退出 wait,lock 保持已持有状态 - 如果用
if而非while,一次虚假唤醒就可能导致访问空队列,触发未定义行为
notify_one() vs notify_all() 的实际影响
在生产者-消费者模型中,多数情况用 notify_one() 就够了。它开销小、可预测性高,尤其当只有一个消费者在等时,避免唤醒所有线程抢锁。
但要注意边界场景:
- 如果消费者逻辑里有多个 condition_variable(比如“有数据”和“缓冲区有空位”共用同一队列),或存在优先级调度,
notify_one()可能唤醒错的等待者 - 当所有消费者都在等,而生产者一次 push 多个元素,用
notify_one()会导致其余消费者继续沉睡,吞吐下降 -
notify_all()不会死锁,但会引发“惊群”,所有等待线程争抢 mutex,其中仅一个能成功进入临界区,其余再次阻塞 —— 在高并发下可能拖慢整体响应
简单模型建议:单生产者 + 单消费者 → notify_one();多消费者且生产者常批量写入 → 考虑 notify_all(),但务必确保谓词检查足够轻量。
别忘了设置停止标志并通知所有等待线程
程序退出前,若还有线程在 wait(),它们会永远卡住。常见做法是引入 std::atomic 作为全局退出信号,并在析构或 shutdown 时设为 true,再调用 notify_all()。
消费者端需同时检查两个条件:
while (!done && queue.empty()) {
cv.wait(lock);
}
if (done && queue.empty()) {
break; // 退出循环
}
- 只靠
notify_all()不够:唤醒后仍要检查done,否则可能取到旧数据后继续等下一次 - 不能只检查
done:万一先设了done = true,但消费者还没开始 wait,就会跳过 wait 直接 break,漏掉队列中残留数据 - 生产者在 push 最后一批数据后,也应调用
notify_all(),确保消费者有机会处理完所有内容
没处理好退出逻辑,是调试时最难复现的 hang 问题之一 —— 它往往只在程序关闭瞬间出现,且不报错。











