C++如何利用std::condition_variable实现精准的线程同步?(并发原语)

1次阅读

std::condition_variable必须与std::mutex配合使用,单独wait会崩溃;须用while循环重检条件防虚假唤醒;notify_one/notify_all需依场景选择;超时等待需注意chrono单位与返回值判断。

C++如何利用std::condition_variable实现精准的线程同步?(并发原语)

std::condition_variable 必须和 std::mutex 一起用,单独 wait 会崩溃

它不是独立同步原语,本质是“条件等待队列”,不维护自身状态。没配 std::mutex 就调 wait(),轻则死锁,重则触发 std::system_error(错误信息通常是 operation not permittedinvalid argument)。

正确姿势:先锁住 std::mutex,再检查条件、调 wait()wait() 内部会自动解锁并挂起线程,被唤醒后重新加锁再返回。

  • 永远用 wait(lock, predicate) 形式,别用无谓的 wait(lock) —— 容易漏掉虚假唤醒
  • predicate 必须是可调用对象Lambda 最常用),且必须访问同一份受保护的共享变量
  • 唤醒方(notify_one() / notify_all())不一定需要持有锁,但持有更安全(避免 notify 早于 wait 的竞态)

虚假唤醒(spurious wakeup)不是 bug,是 POSIX 和 c++ 标准允许的行为

即使没人调 notify_*wait() 也可能返回。这不是实现缺陷,而是底层 futex 或 pthread_cond_wait 的设计取舍。忽略它,程序大概率在高负载或特定内核版本下偶发逻辑错乱。

所以不能写 if (condition == false) cv.wait(lock);,必须用 while 循环重检:

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

while (!data_ready) {     cv.wait(lock); }

这个 while 不是性能浪费 —— 真实场景中虚假唤醒极罕见,但一次漏判就可能让线程跳过本该等待的条件。

notify_one() 和 notify_all() 的选择直接影响吞吐和公平性

用错会导致线程饥饿或资源争抢加剧。比如生产者-消费者模型里,多个消费者等待空缓冲区,只用 notify_one() 没问题;但如果多个线程等同一个完成信号(如 all_tasks_done),用 notify_one() 就会漏掉其余线程。

  • notify_one() 开销小、唤醒精准,适合“一个条件满足 → 一个线程干活”的场景
  • notify_all() 保证不漏,但所有等待线程都竞争锁,可能引发 thundering herd(惊群),尤其在线程数多时明显拖慢
  • 没有“notify_if”这种东西 —— 判断逻辑必须放在 wait 的 predicate 里,而不是靠 notify 侧过滤

std::condition_variable 不支持超时精度控制,chrono 单位选错会误判

wait_for()wait_until() 的超时参数类型是 std::chrono::duration,但很多人传 std::chrono::milliseconds(100) 却期望 100ms 绝对准时 —— 实际上系统调度粒度、锁竞争都会导致延迟。更隐蔽的问题是单位混淆:

// ❌ 错误:把纳秒当毫秒用,实际只等了 100 纳秒 cv.wait_for(lock, std::chrono::nanoseconds(100));  // ✅ 正确:明确单位,且接受“至少等待”语义 cv.wait_for(lock, std::chrono::milliseconds(100));

另外,wait_for() 返回值是 std::cv_status,不是布尔值 —— 必须显式判断是否超时,否则可能把超时当成条件满足。

真正难处理的是“等待带优先级的条件”或“跨进程同步”,std::condition_variable 做不到 —— 它只在单进程内有效,且不提供优先级队列。这时候得换 std::counting_semaphore(C++20)或平台原生机制。

text=ZqhQzanResources