C++怎么使用条件变量_C++线程通信教程【控制】

1次阅读

std::condition_variable必须与std::mutex配合使用,且只能用std::unique_lock包裹;等待必须在while循环中通过谓词检查条件,以应对虚假唤醒和通知丢失;notify_one()和notify_all()需按场景谨慎选择,且通知不保存状态。

C++怎么使用条件变量_C++线程通信教程【控制】

std::condition_variable 必须和 std::mutex 一起用

单独声明 std::condition_variable 没法工作,它不管理共享状态,只负责“通知”和“等待”——而等待时必须持有锁,否则会触发未定义行为。常见错误是漏掉 std::unique_lock<:mutex></:mutex>,或者用 std::lock_guard(它不能转移所有权,wait() 需要能临时释放并重新获取锁)。

  • 必须用 std::unique_lock 包裹 std::mutex,传给 wait()wait_for()
  • 条件检查必须在 wait()Lambda 或谓词中完成,不能在外面“先判断再 wait”,否则有竞态
  • notify_one() 不保证唤醒成功:如果此时没人正在 wait(),通知就丢掉了;需要靠循环条件 + 谓词兜底

虚假唤醒(spurious wakeup)不是 bug,是设计事实

即使没人调用 notify_one()notify_all()wait() 也可能返回。这是 POSIX 和 c++ 标准允许的行为,硬件中断、调度器优化都可能导致。所以永远不要写 if (flag) { wait(); },而要用 while (!flag) { wait(); }

  • 所有 wait() 调用必须包裹在 while 循环里,检查真实业务条件
  • 别试图“避免”虚假唤醒——它无法禁用,只能正确应对
  • 使用带谓词的 wait(lock, pred) 等价于 while (!pred()) wait(lock);,更简洁且不易错

notify_one() vs notify_all():选错会影响性能和逻辑

notify_one() 只唤醒一个等待线程,适合“生产者-消费者”中每次只处理一个任务的场景;notify_all() 唤醒全部,适合多个线程在等同一个状态变更(比如资源初始化完成)。但后者容易引发“惊群”,尤其在线程数多时,所有被唤醒线程都要竞争锁、重检条件,造成不必要的上下文切换。

  • 优先用 notify_one(),除非你明确需要唤醒所有等待者
  • notify_all() 时,确保等待线程的条件检查足够轻量,否则性能会明显下降
  • 注意:notify_* 可以在锁外调用,但若通知后立即修改共享状态,建议锁内通知,避免唤醒后立刻又得等

wait_for() 超时后,条件变量本身不报错,但你要自己处理超时路径

wait_for() 返回的是 std::cv_status 枚举,不是布尔值。常见错误是直接用 if (cv.wait_for(...)) 判断,结果编译不过或逻辑翻车。它返回 std::cv_status::no_timeout 表示被通知唤醒,std::cv_status::timeout 才是超时。

立即学习C++免费学习笔记(深入)”;

  • 必须显式比较返回值:if (cv.wait_for(lk, 100ms) == std::cv_status::timeout)
  • 超时后,仍需检查业务条件是否满足(因为可能刚超时,另一个线程就改了状态)
  • 不要依赖超时来“替代”条件检查——超时只是兜底,不是同步逻辑的主干

最常被忽略的是:条件变量不保存状态,也不记录通知次数。一次 notify_one() 最多影响一个 wait(),早于通知的等待不会被“积压”,晚于通知的等待会一直挂起,直到下次通知。这意味着,如果你的逻辑依赖“通知次数”,就得自己用计数器或队列配合 condition_variable。

text=ZqhQzanResources