线程安全队列需用std::mutex保护push/pop操作,避免仅锁一侧或锁粒度过大;必须配合std::condition_variable防止忙等,且wait条件需防虚假唤醒;循环队列需手写数组+取模逻辑,成员变量应直接初始化。

用 std::queue + std::mutex 实现基础线程安全队列
直接裸用 std::queue 会崩溃,它本身不是线程安全的。必须配合 std::mutex 对入队/出队操作加锁,否则多线程下 push 和 pop 可能同时修改内部指针,触发未定义行为。
常见错误是只锁了 push 或只锁 pop,漏掉一个就大概率 crash;或者把锁粒度设太大(比如整个循环体加锁),导致消费者永远等生产者释放锁,吞吐量骤降。
- 每次
push前调用lock,插入后unlock - 每次
pop前先lock,检查empty()再取值,最后unlock - 避免在锁内做耗时操作(如 I/O、复杂计算),否则阻塞对方线程
为什么不用 std::condition_variable 就容易忙等
如果只靠 while(!q.empty()) 循环检查,消费者线程会持续占用 CPU,即使队列为空也停不下来。这不是“等待”,是“轮询”,浪费资源还拉高负载。
std::condition_variable 是标准解法:消费者在空队列时 wait,生产者 notify_one 唤醒它——唤醒时机精准,不占 CPU。
- 必须和
std::unique_lock<:mutex></:mutex>配合使用,不能用普通std::lock_guard -
wait的 lambda 条件要写成[](const auto& q) { return !q.empty(); },防止虚假唤醒 - 生产者每次
push后必须调用notify_one(单消费者)或notify_all(多消费者)
循环队列需要自己实现,std::queue 不支持固定容量
std::queue 底层默认用 std::deque,容量动态增长,没法限制最大长度。真要“循环队列”,得手写数组+双指针,或用 boost::circular_buffer。
自己实现时最容易错的是边界判断:当 tail == capacity-1 后再 push,tail 要折回 0;head 同理。少一个取模就会越界。
- 用
size_t head, tail, cap三个成员变量管理 -
push前检查(tail + 1) % cap == head判断满;pop前检查head == tail判断空 - 所有索引运算必须带
% cap,别信“不会溢出”的直觉
构造函数里别忘了初始化互斥量和条件变量
很多初学者把 std::mutex 和 std::condition_variable 声明为成员变量,却没意识到它们不可拷贝、不可移动——如果类里没显式定义构造函数,编译器生成的默认构造函数可能不满足要求,尤其涉及多线程时容易静默失败。
- 在类定义里直接初始化:
std::mutex mtx{};、std::condition_variable cv{}; - 不要在构造函数体里赋值,而要用成员初始化列表(如
MyQueue(size_t c) : cap(c), head(0), tail(0) {}) - 如果用了
std::unique_ptr包装队列数据,确保析构时释放,否则内存泄漏
循环队列的“循环”逻辑和线程同步机制是两层独立问题,混在一起调试极难定位。先保证单线程下循环逻辑正确,再加锁和条件变量,否则 bug 会互相掩盖。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











