std::condition_variable 等待 notify_one()/notify_all() 通知,唤醒阻塞的等待线程;必须配 std::unique_lock 以原子地释放锁并等待,唤醒后自动重锁,防止竞态与虚假唤醒。

std::condition_variable 等什么、唤醒谁、为什么必须配 std::unique_lock
它不等时间,也不等某个变量值变化,而是等一个「通知」——notify_one() 或 notify_all() 发出的信号。但它不能自己判断条件是否满足,所以你得在 wait() 前手动检查,且这个检查和挂起必须原子:否则会丢通知。
这就是为什么它强制要求传 std::unique_lock<:mutex></:mutex>:锁住临界区的同时,让 wait() 能安全地释放锁并进入等待;被唤醒后又自动重新加锁,避免检查条件和消费数据之间出现竞态。
常见错误现象:wait() 返回后直接操作共享数据,却没再检查条件是否真满足(虚假唤醒)。
- 永远用 while 循环包裹
wait(),别用 if -
std::shared_mutex或std::shared_lock不行,condition_variable只认unique_lock - 锁对象生命周期必须长于
condition_variable,否则析构时可能还在被 wait
示例关键片段:
立即学习“C++免费学习笔记(深入)”;
std::queue<int> q;
std::mutex mtx;
std::condition_variable cv;
// 消费者
while (true) {
std::unique_lock<std::mutex> lk(mtx);
cv.wait(lk, []{ return !q.empty(); }); // 虚假唤醒也安全
int val = q.front();
q.pop();
lk.unlock();
process(val);
}
生产者怎么 notify 才不丢唤醒、不惊群
notify_one() 随机唤醒一个等待线程,notify_all() 唤醒全部。看似后者更“保险”,但实际多数场景该用 notify_one():生产者每 push 一个元素,只需唤醒一个消费者;用 notify_all() 会导致多个消费者争抢同一个元素,其余线程白唤醒一次(惊群),还可能引发不必要的锁竞争。
容易踩的坑是:在锁外调用 notify_xxx()。虽然语法允许,但若此时没有线程在 wait,通知就丢了;而如果刚好有线程刚释放锁、正要进入 wait,也可能错过。
- 务必在持有同一把
std::mutex的前提下,修改完共享状态(如 push 到队列)后,立刻调用notify_one() - 不要在循环里反复
notify_one()—— 每次只对应一次有效消费 - 如果生产者一次塞多个元素,且希望唤醒多个消费者,应调用
notify_one()多次,而非一次notify_all()
queue.empty() 和 size() 在多线程里为什么不能信
std::queue::empty() 和 size() 都不是原子操作,它们内部可能涉及多个成员访问。即使你用锁保护了 push/pop,单独读 empty() 的结果在解锁后可能已失效——别的线程可能已经 push 或 pop 了。
这也是为什么 condition_variable 的 predicate 必须是 while + lambda:不是靠 empty() 判断,而是靠 wait 返回后再次检查真实状态。
- 永远别写
if (q.empty()) { cv.wait(...); }—— 中间有窗口期 - 别依赖
q.size() == 0当作空判断,行为未定义(某些实现可能抛异常) - 如果你需要精确计数(比如限流),用独立的
std::atomic<size_t></size_t>维护,而不是查 queue
std::condition_variable 的销毁时机很关键
它本身不持有线程,但若还有线程卡在 wait(),你就析构了它或它依赖的 mutex,程序会 undefined behavior(通常是 crash)。这不是理论风险,而是真实发生在线程还没退出、但主线程已 join() 完毕并开始析构全局/局部对象时。
典型场景:类成员变量是 condition_variable,析构函数没等所有工作线程真正退出就返回。
- 确保所有调用
wait()的线程已退出(比如通过标志位 +notify_all()+ join) - 不要在 signal handler、atexit 回调里操作 condition_variable
- 如果用
std::jthread(C++20),利用其自动 join 特性可降低出错概率,但仍需显式通知退出
最稳妥的做法:先设置停止标志,再 notify_all(),再 join(),最后才让 condition_variable 和 mutex 随作用域结束。










