condition_variable 必须与 unique_lock 配合使用,因 wait() 需自动释放并重获锁;notify_one() 适用于单线程唤醒,notify_all() 用于多线程均需继续执行;谓词检查必须用 while 循环防虚假唤醒;其生命周期须长于所有等待线程。

condition_variable 必须和 unique_lock 配合使用
直接用 std::mutex 配 std::condition_variable 会编译失败——wait() 只接受 std::unique_lock<:mutex>,不接受 std::lock_guard 或裸 mutex.lock()。这是因为 wait() 内部需要在阻塞前自动释放锁,并在唤醒后重新加锁,而只有 unique_lock 支持这种可转移、可手动控制的锁定状态。
常见错误是写成:
std::mutex mtx;
std::condition_variable cv;
mtx.lock();
cv.wait(mtx, []{ return ready; }); // ❌ 编译报错:no matching member function
正确写法必须用 unique_lock:
- 声明时直接绑定:
std::unique_lock<:mutex> lk(mtx); - 传给
wait():cv.wait(lk, []{ return ready; }); -
lk在wait返回时已重新持有锁,无需额外加锁
notify_one() 和 notify_all() 的选择取决于等待者语义
如果只有一个线程在等某个特定条件(比如生产者唤醒一个消费者取走一项任务),用 notify_one() 更高效;但如果多个线程在等同一个条件、且该条件满足后**所有等待者都应继续执行**(比如“所有工作线程开始处理新批次”),就必须用 notify_all()。
容易踩的坑:
-
notify_one()不保证唤醒“最先等待的那个”,只是唤醒一个未指定的等待线程 - 调用
notify_*时,mutex是否已解锁不影响行为,但习惯上在修改完共享状态后、尽快通知,避免锁粒度扩大 - 如果在
notify_one()后,被唤醒线程发现条件其实不成立(虚假唤醒或条件已被其他线程消费),它会继续等待——所以wait()的谓词必须是 while 循环逻辑,不能只用 if
虚假唤醒(spurious wakeup)必须用 while 循环检查条件
POSIX 和 C++ 标准都允许 condition_variable::wait() 在没有对应 notify 的情况下返回,这叫虚假唤醒。忽略它会导致逻辑错误:比如消费者被唤醒却发现队列仍为空。
正确模式永远是:
std::unique_lock<:mutex> lk(mtx);
while (!ready) { // ❌ 不能用 if
cv.wait(lk);
}
// 此时 ready 一定为 true
</:mutex>
原因:
-
wait()返回只表示“可能有事发生”,不保证条件成立 - 用
while能兜底二次检查,失败则继续等待 - 即使没有虚假唤醒,也可能出现“唤醒后条件被别的线程抢先修改”的竞争(例如两个消费者同时被
notify_all唤醒,但只有一个能取走唯一元素)
condition_variable 的生命周期必须长于所有等待线程
如果 std::condition_variable 对象在还有线程处于 wait() 状态时被析构,程序会崩溃(通常是 SIGABRT 或未定义行为)。这不是资源泄漏,而是对已销毁对象的非法访问。
典型场景:
- 把
condition_variable声明为局部变量,却在线程中调用wait() - 类成员变量,但对象在子线程还在 wait 时就被销毁(比如主线程提前
delete或离开作用域)
安全做法:
- 确保所有等待线程已退出(通过
join())后再销毁condition_variable - 或使用
std::shared_ptr管理,让生命周期与线程绑定 - 更简单的是:把它作为全局/静态变量,或严格绑定到整个程序生命周期的对象中
真正难调试的点往往不在语法,而在“谁在等、谁在通知、谁先结束”——这三个角色的时间关系一旦错位,wait() 就可能永远卡住,或者 notify() 发出后无人接收。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











