condition_variable必须与std::mutex配合使用,wait()要求传入已加锁的std::unique_lock以实现原子释放锁与挂起,需用while循环检查谓词防虚假唤醒,notify_one()唤醒一个线程,notify_all()唤醒所有等待线程。

condition_variable 本身不提供互斥保护,也不保存状态,它必须和 std::mutex 配合使用,否则直接调用 wait() 会抛出 std::system_error(错误码为 operation_not_permitted)。
为什么 wait() 必须传入已加锁的 mutex
这是 最常踩的坑:很多人以为 wait() 会自己上锁,其实它只负责「原子地释放锁 + 挂起线程」。如果传入未加锁的 std::unique_lock,wait() 会在内部调用 unlock() 导致未定义行为。
-
wait()的原子操作本质是:unlock() → 等待唤醒 → lock(),中间不能被中断 - 必须用
std::unique_lock<:mutex></:mutex>(不能用std::lock_guard),因为后者不可移动、无法在wait()中临时释放 - 唤醒后重新获得锁,才能安全检查谓词(predicate)是否真正满足——这叫「虚假唤醒(spurious wakeup)防御」
notify_one() 和 notify_all() 的实际区别
二者都只是「发信号」,不阻塞、不等待线程响应,但唤醒时机和适用场景差异很大:
-
notify_one()只唤醒一个正在wait()的线程(具体哪个由调度器决定),适合「生产者-消费者」中单个任务就绪的场景 -
notify_all()唤醒所有等待线程,适合「状态变更影响多个依赖者」,比如 barrier、条件重置、或共享资源可用性变化 - 注意:
notify_*可以在 mutex 持有或释放状态下调用,但若在持有锁时调用,被唤醒线程需等当前锁释放才能真正进入临界区——这可能造成不必要的延迟
必须用 while 循环检查谓词,不能用 if
哪怕你确定只唤醒一个线程,也必须用 while (cond == false) 包裹 wait(),否则可能因虚假唤醒或逻辑竞争导致跳过检查。
立即学习“C++免费学习笔记(深入)”;
// ✅ 正确:循环检查
std::unique_lock<std::mutex> lk(mtx);
while (!ready) {
cv.wait(lk);
}
// do work...
// ❌ 危险:if 可能漏掉条件变化或虚假唤醒
if (!ready) {
cv.wait(lk); // ready 已为 true,但线程仍挂起
}
- 虚假唤醒是 POSIX 和 C++ 标准允许的行为,不保证唤醒一定对应
notify_* - 即使没虚假唤醒,也可能出现「notify 发生在 wait 之前」:先改
ready = true,再cv.notify_one(),但此时无人在 wait,信号丢失;之后线程才开始wait(),就会永久阻塞——所以谓词必须是共享状态,且检查必须在锁内完成
wait_for() / wait_until() 的超时陷阱
带超时的等待看似安全,但返回值不是布尔值而是 std::cv_status,容易误判:
-
wait_for()返回std::cv_status::no_timeout表示被正常唤醒,timeout才表示超时 - 超时后,谓词仍需手动检查,不能假设「超时 = 条件不满足」——可能刚超时,另一线程就设置了条件并 notify,但本线程已退出循环
- 超时时间用
std::chrono::steady_clock,避免系统时间调整干扰;不要用system_clock做相对等待
condition_variable 的核心约束很朴素:锁、谓词、唤醒三者必须严格配对。最容易忽略的是——notify_* 调用本身不保序,也不保量,一切同步语义都靠程序员用 mutex + 共享变量显式建模。


















