
std::condition_variable 必须和 std::mutex 一起用
单独使用 std::condition_variable 会触发未定义行为,因为它不管理共享状态的互斥访问。它只负责线程挂起与唤醒,而状态(比如缓冲区是否为空 / 满)的读写必须由配套的 std::mutex 保护。
常见错误是只对 notify_one() 或 wait() 加锁,却忘了在检查条件前也加锁——这会导致竞态:检查完“缓冲区非空”,但还没调用 wait() 就被另一个线程取走最后一个元素,结果消费者永久阻塞。
正确做法是把「条件检查 + wait」包进同一个临界区,或更推荐地,用带谓词的 wait(lock, pred) 形式,它内部自动完成解锁 - 等待 - 重锁 - 重检查循环:
std::unique_lock lock(mtx); cv.wait(lock, [&] {return !buffer.empty(); }); // 自动重检
生产者用 notify_one() 还是 notify_all()?
多数场景下用 notify_one() 就够了。它只唤醒一个等待中的线程,避免惊群效应(thundering herd),尤其当多个消费者在等“非空”、多个生产者在等“非满”时,唤醒多余线程只会立刻再次进入等待,徒增调度开销。
立即学习“C++ 免费学习笔记(深入)”;
只有以下情况才考虑 notify_all():
- 你无法确定哪个等待线程能推进当前状态(例如多个条件混用在同一
condition_variable上) - 使用了虚假唤醒(spurious wakeup)不可控的老式平台(现代 libc++ / libstdc++ 已较好处理)
- 缓冲区从满变非满后,需要同时唤醒一个生产者和一个消费者(极少见,通常应拆成两个 cv)
注意:notify_one() 不保证唤醒的是消费者而非生产者——所以你的等待谓词必须严格匹配逻辑,不能依赖唤醒顺序。
缓冲区满 / 空判断必须用 while 而非 if
即使用了带谓词的 wait(),底层仍可能发生虚假唤醒(spurious wakeup)。这意味着线程被唤醒时,条件未必成立。若用 if 检查后直接消费,可能触发 buffer.front() 在空容器上调用,导致崩溃。
安全写法永远是:
std::unique_lock lock(mtx); cv.wait(lock, [&] {return !buffer.empty(); }); // 内部就是 while 循环 // 此时 buffer 确实非空,可安全取值 auto item = buffer.front(); buffer.pop();
如果你手写裸 wait(),必须显式用 while:
std::unique_lock lock(mtx); while (buffer.empty()) {cv.wait(lock); }
别忘了 notify 的时机和位置
notify_one() 应该在修改共享状态之后、且仍在持 有锁 的情况下调用——这不是必须的,但强烈推荐。原因有二:
- 避免唤醒后消费者立即抢锁失败,被迫再次等待(虽然合法,但低效)
- 某些实现中,如果在锁外 notify,而此时目标线程刚被调度器选中准备 wait,可能错过通知(尽管标准不保证,但实践上容易出问题)
典型生产者片段:
std::unique_lock lock(mtx); while (buffer.size() >= capacity) {not_full.wait(lock); } buffer.push(item); not_empty.notify_one(); // ✅ 在锁内、状态已更新后
反过来,消费者中 not_full.notify_one() 也要在 pop() 后、锁未释放前调用。
最容易被忽略的一点:如果生产者或消费者在退出前没有清理或广播状态(比如程序结束前清空缓冲区),残留的等待线程可能永远卡住。实际项目中建议配合 std::atomic 做退出协调,而不是依赖 notify。






























