线程安全队列需用std::mutex保护所有队列操作,std::condition_variable实现空等待/非空唤醒;生产者每次push后notify_one,消费者wait谓词必须检查!q.empty()或stop_flag;终止时设stop_flag并notify_all,确保所有线程协作退出。

用 std::queue + std::mutex + std::condition_variable 实现线程安全队列
单队列生产者消费者模型的核心是保证多线程下队列读写不冲突,且消费者能及时感知新数据。C++ 标准库的 std::queue 本身不是线程安全的,必须配合 std::mutex 控制访问,再用 std::condition_variable 实现“空时等待、有数据唤醒”的同步逻辑。
常见错误是只加锁不等待,导致消费者忙轮询或死锁;或者误用 notify_one() / notify_all() 导致唤醒丢失或过度唤醒。
- 生产者每次
push()后必须调用notify_one(),不能只在队列从空变非空时才通知(否则多个生产者并发 push 可能漏通知) - 消费者必须在
wait()的谓词里检查队列是否非空,因为可能被虚假唤醒 -
std::queue不支持迭代器遍历,也不提供size()的原子性保证,别依赖q.size() > 0代替!q.empty()
消费者阻塞等待的正确写法:用 wait() 谓词而非手动 while 循环
直接在 lock 后判断 queue.empty() 然后 unlock + sleep 是错的——这会浪费 CPU 且无法精确响应生产者通知。
正确做法是把条件判断写进 wait() 的 lambda 里,让条件变量自动处理等待和唤醒:
立即学习“C++免费学习笔记(深入)”;
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [&] { return !q.empty(); }); // 自动释放锁、等待、重新获取锁、再检查条件
auto item = std::move(q.front());
q.pop();
这个写法确保:只有队列真非空时才继续执行,且整个过程无竞态。如果写成 while (q.empty()) { cv.wait(lock); },就可能因虚假唤醒跳过条件检查,导致 front() 崩溃。
如何安全终止所有线程:用 stop_flag + notify_all()
没有退出机制的生产者消费者模型在程序结束时会卡死。不能靠销毁 std::thread 强制终止,必须协作式退出。
- 定义一个
std::atomic<bool> stop_flag{false};全局控制信号 - 生产者在循环中检查
if (stop_flag) break;,并在退出前调用cv.notify_all() - 消费者
wait()的谓词要同时检查!q.empty() || stop_flag.load(),避免永久阻塞 - 主线程设置
stop_flag = true;后,必须join()所有子线程,否则析构未 join 的std::thread会调用std::terminate()
为什么不用 std::deque 或自己封装锁?
std::queue 默认底层是 std::deque,它支持两端高效插入删除,但 std::queue 接口只暴露了单端操作,语义更贴合“队列”概念,不易误用。自己封装带锁的 queue 类看似封装性好,但容易把 mutex 和 condition_variable 生命周期搞错——比如把它们放在局部作用域,或没处理好异常安全。
真正容易被忽略的是:所有对队列的访问(empty()、front()、pop()、push())都必须在同一个 std::mutex 下保护,哪怕只是读 empty()。漏掉任意一处,就会出现 front() 前队列非空、执行时却为空的崩溃。


















