正确使用std::condition_variable需配合std::mutex:notify_one/all必须在std::unique_lock持有锁时调用,wait必须用while循环检查条件以防虚假唤醒,退出时需原子标志+notify_all确保安全终止。

用 std::condition_variable 配合 std::mutex 才算真正唤醒等待线程
很多人写完发现生产者发了信号,消费者却没醒——根本原因是没在 std::unique_lock 持有锁的状态下调用 notify_one() 或 notify_all()。条件变量不是“发个通知就完事”,它依赖互斥量的释放与重入机制:消费者在 wait() 中会原子地释放锁并挂起;生产者必须在持有同一把锁的前提下调用 notify_*,才能确保唤醒后消费者能立刻抢到锁、检查条件。
常见错误现象:wait() 一直不返回,或只唤醒一次后卡死。
- 务必用
std::unique_lock<:mutex></:mutex>构造wait()的第一个参数,不能传std::lock_guard -
notify_one()足够用于单消费者场景;多消费者且需公平唤醒时才考虑notify_all()(但注意虚假唤醒增多) - 唤醒操作本身不需要锁保护,但必须紧邻修改共享状态之后、且仍在锁区内完成(比如往队列 push 后立刻 notify)
wait() 必须用 while 循环判断条件,不能用 if
虚假唤醒(spurious wakeup)是 POSIX 和 C++ 标准明确允许的行为:线程可能在没收到 notify 的情况下自己醒来。如果只用 if (queue.empty()) wait(...),醒来后直接消费,极大概率 crash 或读到空数据。
使用场景:所有基于条件变量的等待逻辑,无论队列、标志位还是计数器。
立即学习“C++免费学习笔记(深入)”;
- 正确写法:
while (queue.empty()) cond_var.wait(lock); - 不要把条件检查拆到 wait 外面,否则竞态:检查完为空 → 被调度出去 → 生产者插入并 notify → 你再 wait 就永远等下去
- 条件表达式里只放可被 notify 前后改变的共享变量,避免逻辑耦合过深
缓冲区用 std::queue 还是 std::deque?别忽略移动语义成本
用 std::queue 默认底层是 std::deque,看似省事,但如果你存的是大对象(比如 std::vector<int></int>),每次 pop() 都会触发一次拷贝——而消费者线程本该专注处理,不该被内存搬移拖慢。
性能影响:在高频小消息场景下差异不大;一旦单条消息 >1KB,吞吐量可能掉 30%+。
- 优先用
std::queue<:unique_ptr>></:unique_ptr>或std::queue<t></t>+std::move()出队(T item = std::move(q.front()); q.pop();) - 避免
std::queue<:string></:string>直接存长字符串——改用std::string_view+ 外部生命周期管理,或 move - 如果确定只用单线程消费,且消息类型 trivial,
std::vector环形缓冲区比std::queue更省内存和 cache
程序退出时消费者还在 wait 怎么安全终止?
主线程结束前,若消费者线程还卡在 wait(),直接析构 std::condition_variable 会 UB(C++20 前未定义行为)。必须让等待线程有机会跳出循环、自然退出。
容易踩的坑:用全局 bool flag + notify_all(),但没加内存序,导致消费者永远看不到 flag 变化。
- 声明停止标志为
std::atomic<bool> done{false};</bool>,消费者循环条件改为while (!done && queue.empty()) - 主线程退出前设
done = true;,再调用cond_var.notify_all(); - 消费者线程 join 前,确保生产者已停、所有 notify 发出、且消费者已跳出 wait 循环(可加超时 wait 防死锁)
最麻烦的其实是多个条件共存:比如既要等非空,又要等缓冲区未满。这时候一个 condition_variable 不够用,得拆成两个,或者统一用一个但条件判断更复杂——稍不注意就 deadlock 或漏唤醒。











