
std::condition_variable 必须和 std::mutex 一起用
单独声明 std::condition_variable 没有意义,它不管理共享状态,只负责线程等待/唤醒。所有 wait()、notify_one()、notify_all() 调用都必须在持有同一把 std::mutex 的前提下进行——不是语法强制,但逻辑上必须如此,否则会触发未定义行为(比如虚假唤醒后读取脏数据)。
典型错误是:在 wait() 前没加锁,或锁的是另一把互斥量;更隐蔽的是用 std::shared_mutex 或自定义锁类型,而 std::condition_variable 只接受 std::unique_lock<:mutex>。
-
wait()内部会自动释放传入的std::unique_lock,唤醒后重新获取锁再返回 - 永远用
wait(lock, predicate)形式,避免虚假唤醒;不要用无谓词的wait(lock) - 唤醒操作(
notify_one()/notify_all())本身不需要持锁,但修改被等待的条件变量(如 flag、queue 等)时必须持锁
正确写法:带谓词的 wait + 双重检查
所谓“谓词”,就是封装了你要等待的业务条件的 lambda 或函数对象。它会在每次被唤醒后自动执行,只有返回 true 才退出 wait();返回 false 则继续等待。这天然解决了虚假唤醒问题,也省去手写 while 循环的冗余。
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
// 线程 A:等待就绪
std::thread t1([&]{
std::unique_lock lock(mtx);
cv.wait(lock, [&]{ return ready; }); // 谓词自动重入检查
std::cout << "go!\n";
});
// 线程 B:通知就绪
std::thread t2([&]{
std::this_thread::sleep_for(100ms);
{
std::lock_guard lock(mtx);
ready = true;
}
cv.notify_one(); // 唤醒一个等待者
});
t1.join(); t2.join();
notify_one() 和 notify_all() 的选择陷阱
表面看 notify_one() 更轻量,但选错会导致死锁或饥饿。关键看等待者是否“等同”:如果多个线程在等同一个条件(比如队列非空),且消费逻辑是互斥的(一次只取一个任务),那用 notify_one() 完全够用;但如果条件满足后多个线程都能推进(比如 barrier 等待、资源池扩容),就必须用 notify_all()。
立即学习“C++免费学习笔记(深入)”;
- 用
notify_one()却有多个线程在等不同条件(比如等不同 ID 的响应),会导致其他线程永远等不到唤醒 - 用
notify_all()在高并发下可能引发“惊群”,但现代 libc++/libstdc++ 对此做了优化,实际开销可控 -
notify_*()不保证唤醒顺序,也不保证唤醒后立即调度;被唤醒线程仍需竞争 mutex,所以不能假设唤醒即执行
常见崩溃点:析构前确保无等待线程
std::condition_variable 对象析构时,若仍有线程在 wait(),行为未定义(通常 crash)。这不是资源泄漏,而是设计约束:你必须自己保证,在销毁前所有等待线程已退出或被唤醒。
- 典型做法是设置一个
std::atomic,在析构前设为shutdown{false} true并调用notify_all() - 等待线程的谓词中要同时检查业务条件和
shutdown标志 - 不要依赖 RAII 自动清理——
std::condition_variable没有“joinable”语义,它不管理线程生命周期
最易忽略的是:主线程销毁 cv 时,子线程可能刚进入 wait() 就被析构,连谓词都来不及执行。务必让线程退出逻辑与 cv 生命周期对齐。











