std::condition_variable 必须与 std::unique_lock 配合使用,不可单独声明或搭配 lock_guard/裸锁;必须用带谓词的 wait() 避免虚假唤醒;notify_one() 适用于竞争关系,notify_all() 适用于协作关系;不适用于简单信号传递或高频标志访问,且生命周期须覆盖所有等待线程。

condition_variable 必须和 unique_lock 配合用
单独声明 std::condition_variable 没用,它不管理互斥,也不持有锁。常见错误是试图用 std::lock_guard 或裸 mutex.lock()/unlock() 配合 wait() —— 这会直接触发未定义行为,程序可能崩溃或死锁。
原因很简单:wait() 内部要原子地释放锁并挂起线程,唤醒后又要自动重拿锁;只有 std::unique_lock 支持中途释放和重新获取,lock_guard 一旦构造就不能手动 unlock。
- 必须用
std::unique_lock<:mutex></:mutex>构造,传给wait() - 不要在
wait()前手动unlock(),也不要指望它“帮你省事” -
wait()返回时,unique_lock一定已重新加锁(哪怕被虚假唤醒)
wait() 的 predicate 版本不是可选优化,而是必须用
写成 cv.wait(lock) 是危险的:它可能被虚假唤醒(spurious wakeup),导致线程在条件根本没满足时就继续执行,引发数据竞争或逻辑错误。
正确做法是始终用带谓词的重载:cv.wait(lock, [&]{ return ready; });。这个 lambda 会在每次唤醒后自动检查,不满足就继续 wait,把虚假唤醒完全屏蔽掉。
立即学习“C++免费学习笔记(深入)”;
- 别图省事写空括号版本,C++ 标准明确允许虚假唤醒
- 谓词里访问的变量(如
ready)必须受同一 mutex 保护 - lambda 捕获方式建议用
[&],避免值捕获导致检查过期状态
notify_one() 和 notify_all() 的选择取决于等待者语义
用错通知方式会导致线程饥饿或性能浪费。notify_one() 只唤醒一个等待线程,适合“生产者-消费者”中单个任务就绪的场景;notify_all() 唤醒全部,适合“状态变更影响所有等待者”的情况(比如取消标志置位)。
常见坑是:明明只有一个新任务,却调用 notify_all(),结果多个消费者线程同时醒来争抢,最后只有一人成功,其余又立刻回去 wait——白费系统调度开销。
- 如果等待者之间是“竞争关系”(如抢队列头),优先
notify_one() - 如果等待者是“协作关系”(如都等某个初始化完成),必须
notify_all() -
notify_*()可以在锁外调用,但要注意:若在锁内调用,被唤醒线程能更快拿到锁(减少上下文切换)
condition_variable 不是万能同步原语,别用来替代 atomic 或 future
有人一看到“线程间通信”就本能上 condition_variable,结果把简单问题复杂化。比如只是传递一个布尔标志、计数器或一次性结果,std::atomic<bool></bool> 或 std::promise/std::future 更轻量、无锁、更易推理。
condition_variable 的真实定位是:协调「长期等待某条件成立」的线程,且该条件本身需要互斥保护(比如容器非空、缓冲区有空间)。它自带锁管理开销,也要求你严格配对 wait/notify。
- 单次信号传递?用
std::promise+std::future::wait() - 高频读写共享标志?用
std::atomic,别绕一圈进 mutex - 等待超时?
wait_for()或wait_until()支持,但记得检查返回值是否为std::cv_status::timeout
最常被忽略的一点:condition_variable 的生命周期必须长于所有可能调用 wait() 的线程。提前析构它,而还有线程在 wait 中,就是未定义行为——不是报错,而是静默崩溃或卡死。别把它放在局部作用域里,除非你能 100% 确保所有 wait 已结束。











