std::condition_variable必须与std::mutex配合使用,因其不管理共享状态;正确用法是用unique_lock保护条件判断并作为谓词传入wait,notify_one适用于多数场景,有界队列需双cv分离空满条件。

std::condition_variable 本身不保证线程安全,必须配 std::mutex 使用
很多人一上来就只用 std::condition_variable 调用 wait() 或 notify_all(),结果出现随机崩溃或死锁。根本原因是:std::condition_variable 不管理共享状态,它只负责阻塞/唤醒,而对共享数据(比如队列是否为空、是否满)的读写必须由外部 std::mutex 保护。
典型错误是:在 wait() 前没加锁,或在判断条件时没用锁保护——这会导致竞态条件(race condition),例如消费者刚检查完队列为空,生产者立刻 push 一个元素并 notify,但消费者已经跳过 wait 进入下一轮循环,导致丢失通知。
正确做法是:
-
std::unique_lock<:mutex></:mutex>必须在调用wait()前已持锁,且传入该锁对象 - 条件判断(如
queue.empty())必须在锁保护下进行,且作为wait()的谓词(lambda 或函数对象)传入,不能先判断再 wait - 所有对共享队列的
push()/pop()操作都必须在同一个std::mutex保护下
多生产者多消费者场景下,notify_one() 和 notify_all() 的选择很关键
用 notify_one() 还是 notify_all(),直接影响吞吐量和唤醒开销。不是“越全越好”,也不是“越少越省”——得看你的等待逻辑是否允许“虚假唤醒”或“条件变更后仍需重检”。
对于标准的无界或有界阻塞队列:
- 生产者调用
notify_one()即可唤醒一个等待消费的线程;多个消费者都在 wait,唤醒一个足够,其余继续等下次通知 - 消费者调用
notify_one()同理,唤醒一个等待生产的线程 - 只有当共享状态变化可能同时满足多个等待线程的条件时(比如批量插入后队列从空变非空,多个消费者都卡在 empty() 上),才考虑
notify_all();但要注意,这会引发“惊群效应”,尤其在线程数多时显著拖慢性能
示例片段(生产者端):
std::unique_lock<:mutex> lk(mtx); queue.push(item); lk.unlock(); // 尽早释放锁,避免阻塞其他生产者 cond_var.notify_one(); // 只唤醒一个消费者</:mutex>
避免 spurious wakeup 导致逻辑错乱
std::condition_variable::wait() 允许虚假唤醒(spurious wakeup),即没被 notify 也返回。这不是 bug,是 POSIX 和 C++ 标准允许的实现优化。如果代码依赖“唤醒一定意味着条件成立”,就会出错。
所以永远不要写:
lk.lock(); if (queue.empty()) cond_var.wait(lk); // ❌ 错误:没用谓词,无法重检
必须用带谓词的 wait:
std::unique_lock<:mutex> lk(mtx);
cond_var.wait(lk, [&] { return !queue.empty(); }); // ✅ 正确:每次唤醒都重检条件</:mutex>
这个 lambda 在每次被唤醒(无论真假)时都会执行,返回 false 则自动继续 wait,返回 true 才退出。这是多生产者多消费者模型稳定运行的底线保障。
有界队列中,生产者和消费者要分别等待不同条件
无界队列只需关心“非空”或“非满”,但实际系统往往需要限流(比如固定大小的环形缓冲区)。这时不能共用一个 std::condition_variable,否则会出现“生产者唤醒消费者,消费者又唤醒生产者”的乒乓等待,或者漏通知。
推荐做法是使用两个 condition variable:
-
not_empty_cv:供消费者 wait,生产者在 push 后 notify_one() -
not_full_cv:供生产者 wait,消费者在 pop 后 notify_one()
对应地,判断条件也要拆开:
- 消费者 wait 条件:
!queue.empty() - 生产者 wait 条件:
queue.size()
注意:这两个 cv 必须和同一个 std::mutex 配合使用,不能为每个 cv 配独立 mutex —— 否则无法原子地更新 size 并决定 notify 哪个 cv。
最易被忽略的是:即使逻辑写对了,如果忘了在析构前调用 notify_all() 清理等待线程,程序退出时可能 hang 在 wait 上。尤其在多生产者多消费者长期运行的服务中,shutdown 流程必须显式唤醒所有等待线程并 join。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











