最直接可靠的方式是用 std::mutex 保护共享队列、std::condition_variable 控制等待逻辑,且 wait() 必须在 unique_lock 持有锁时调用,并用 while 循环检查条件以防虚假唤醒;notify 须在状态变更后、解锁前执行,多条件变量需共用同一把锁。

用 std::mutex 和 std::condition_variable 实现基础同步
最直接可靠的方式是组合使用 std::mutex 保护共享队列,用 std::condition_variable 控制等待逻辑。关键不是“加锁就行”,而是必须让 wait() 在持有互斥锁的前提下调用,否则会抛出 std::system_error: Operation not permitted。
常见错误是把 wait() 放在 lock() 之前,或在 wait() 返回后没再次检查条件(虚假唤醒)。正确写法必须用 while 循环包裹 wait():
std::unique_lock<:mutex> lock(mtx);
cond.wait(lock, [&] { return !queue.empty(); }); // 必须是 lambda,且循环判断</:mutex>
生产者同理:用 cond.notify_one() 或 notify_all() 唤醒等待线程,但无需在每次插入后都 notify——只有从空变非空时才需通知消费者;同理,消费者只在从满变非满时通知生产者(若队列有容量限制)。
为什么不能只用 std::mutex?
仅靠互斥锁能防数据竞争,但无法解决“忙等”或“无意义轮询”。比如消费者发现队列为空,解锁、sleep、再锁、再查……这既浪费 CPU,又无法精准响应新数据到达。而 condition_variable 让线程真正挂起,直到被显式唤醒。
注意:std::condition_variable 必须搭配 std::unique_lock<:mutex></:mutex> 使用,不能配 std::lock_guard(后者不支持 unlock/lock 的灵活控制),也不能用普通 std::mutex 对象直接传入 wait()。
典型陷阱:
- 用
std::lock_guard锁住后调cond.wait()→ 编译失败 - 忘记在
wait()前加锁 → 运行时报错 - 唤醒后不重新检查条件 → 可能处理空队列或越界读取
带容量限制的队列要注意什么?
真实场景中队列通常有上限(比如缓冲区大小),这时消费者和生产者要共用两个条件变量:not_empty 和 not_full,分别对应“可消费”和“可生产”状态。
关键点在于:两个条件变量共享同一把锁,且每次 notify 都必须在修改共享状态之后、释放锁之前完成。例如生产者插入前要等 not_full,插入后 notify not_empty;消费者取出前等 not_empty,取出后 notify not_full。
参数差异小但影响大:notify_one() 更轻量,适合一对一唤醒;notify_all() 更安全,尤其当多个线程可能因不同原因阻塞在同一条件变量上时。除非确定只有一个等待者,否则优先选 notify_all()。
避免死锁和资源泄漏的实际做法
死锁常发生在多条件变量+多锁场景,但单队列模型下主要风险来自异常路径:比如生产者在 push 时抛异常,导致未 notify 消费者,或消费者在 pop 后未及时 notify 生产者。
解决方案很实在:
- 所有共享操作(push/pop)封装进函数,确保 notify 跟在状态变更之后,且不受异常干扰
- 用
std::unique_lock的 RAII 特性自动管理锁生命周期,别手动unlock() - 如果队列类型是
std::queue<:shared_ptr>></:shared_ptr>,注意pop()后立刻 move 出值,避免智能指针在锁内延长引用计数
最容易被忽略的是:条件变量的 wait 返回不代表“一定有数据”,只代表“被唤醒了”。必须配合 while 循环 + 条件检查,这是 C++ 标准库的设计约定,绕不开。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











