最简可靠模型是std::mutex配std::condition_variable:所有队列访问须加锁,等待必须用带谓词的wait/ wait_for,notify_one前更新状态,避免虚假唤醒和竞态。

用 std::mutex + std::condition_variable 实现最简可靠模型
别碰 std::semaphore(C++20 才有,且 Windows 上实现不稳),也别手写自旋锁。标准库里唯一推荐的组合就是 std::mutex 配 std::condition_variable —— 它天然支持“等待条件成立”,正好对应消费者等数据、生产者等空位的核心逻辑。
关键不是加锁,而是“等什么、什么时候醒”。消费者必须在队列非空时才取;生产者必须在队列未满时才塞。这两个判断不能脱离锁,否则存在竞态:比如检查完非空,还没来得及取,就被另一个线程取走了。
- 始终用
std::unique_lock<:mutex></:mutex>构造条件变量的等待,不能用std::lock_guard -
wait()的谓词(lambda)必须是“真实条件”,不要写成while (queue.empty()) wait()这种裸循环,容易被虚假唤醒搞崩 - 每次
notify_one()前,确保共享状态已更新(比如 push 后再 notify),否则唤醒了也取不到东西
避免 std::queue 在多线程下崩溃的三个硬约束
std::queue 本身不是线程安全的 —— 它的 empty()、front()、pop() 全部需要外部同步。常见错误是只锁了 push(),却让消费者直接调 q.front(),结果段错误或读到垃圾值。
- 所有对队列的访问(包括
empty()、size()、front()、back()、pop()、push())必须包裹在同一把std::mutex下 - 不要在锁外保存
front()返回的引用或指针 —— 锁一释放,对象可能已被另一个线程pop()掉 - 如果要用
size()判断容量,记得它和empty()一样只是快照,不能替代条件变量的谓词
为什么不用 notify_all() 而用 notify_one()
多数场景下,一个新元素只够喂一个消费者;一个空位也只够一个生产者填。用 notify_all() 会唤醒所有等待线程,它们抢锁、检查条件、发现不满足又回去睡 —— 白耗 CPU,还可能引发惊群效应(thundering herd)。
立即学习“C++免费学习笔记(深入)”;
只有两种情况才考虑 notify_all():
- 你明确知道多个线程必须同时响应同一个事件(比如全局 shutdown 信号)
- 条件逻辑复杂,无法精确判断该唤醒谁(但这时更该重构逻辑,而不是滥用
notify_all)
绝大多数生产者-消费者代码里,notify_one() 是默认且安全的选择。
带超时的等待怎么写才不丢数据
用 wait_for() 或 wait_until() 时,返回值是 std::cv_status,不代表条件是否成立 —— 它只表示“是因为超时退出,还是因为被唤醒”。真正要判断的,还是谓词本身。
- 错误写法:
if (cv.wait_for(lk, 100ms) == std::cv_status::no_timeout) { /* 取数据 */ }—— 谓词可能仍为 false - 正确写法:始终用带谓词的
wait_for(lk, timeout, []{ return !queue.empty(); }),返回 true 才安全取 - 超时后若继续循环,注意重置超时时间(比如用
steady_clock::now() + 100ms),别让累积误差导致等待越来越短
边界情况最容易出问题:比如消费者超时醒来,发现队列仍空,但此时生产者正卡在锁里准备 push —— 这就是为什么谓词检查必须和等待原子绑定,不能拆开。










