C++怎么实现生产者消费者_C++多线程同步模型【协作】

1次阅读

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

C++怎么实现生产者消费者_C++多线程同步模型【协作】

std::mutex + std::condition_variable 实现最简可靠模型

别碰 std::semaphorec++20 才有,且 windows 上实现不稳),也别手写自旋锁。标准库里唯一推荐的组合就是 std::mutexstd::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)。

只有两种情况才考虑 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 —— 这就是为什么谓词检查必须和等待原子绑定,不能拆开。

text=ZqhQzanResources