必须用 std::mutex + std::condition_variable 实现线程安全队列:mutex 保护共享缓冲区,condition_variable 解决“等待条件满足”问题;仅用 mutex 无法阻塞等待,易致忙等或状态竞争;std::queue 非线程安全,须封装并统一加锁;shared_mutex 不适用生产者-消费者模型,因 push/pop 均为写操作。

用 std::mutex + std::condition_variable 实现线程安全队列
核心是保护共享缓冲区,避免生产者写入时消费者读取到中间状态。必须用 std::mutex 锁住队列操作,再用 std::condition_variable 控制等待逻辑:生产者等“非满”,消费者等“非空”。
常见错误是只锁了 push 或 pop 但没锁整个判断+操作流程,比如先 if (!queue.empty()) 再 queue.front() —— 这中间可能被另一个线程改掉状态,导致 front() 抛 std::out_of_range。
实操建议:
- 把
push()和pop()封装成队列类的成员函数,内部统一加锁、通知 - 用
wait()的谓词重载(带 lambda)代替裸wait(),避免虚假唤醒 - 生产者调用
notify_one()即可(只需唤醒一个消费者),但若用notify_all()也没错,只是稍重
为什么不能只用 std::mutex?
因为互斥锁只能保证“不同时访问”,不能解决“等条件满足再继续”。比如缓冲区满了,生产者得停;空了,消费者得停——这需要阻塞等待,而 std::mutex 拿不到锁只会忙等或直接失败,既耗 CPU 又无法精准同步。
典型表现是:不用 std::condition_variable 时,你得手写 while 循环 + std::this_thread::yield(),但 yield 不保证让出时间片,还可能饿死某个线程。
正确做法是:wait() 自动释放锁、挂起线程、被唤醒后重新抢锁——三步原子完成。
std::queue 本身不是线程安全的,但可以安全封装
std::queue 是个适配器,默认底层用 std::deque,它所有成员函数都不带线程安全保证。这意味着你不能在多个线程里直接调它的 empty()、size()、push() 等。
但你可以把它包进一个类,所有对外接口都加锁。注意两点:
-
empty()和size()在加锁后调用才可靠;裸调返回值可能瞬间过期 - 不要返回引用(如
front()返回T&),因为锁一释放,对象可能已被另一个线程 pop 掉——应返回值(T)或用std::optional<t></t>表示可能为空
使用 std::shared_mutex 优化多消费者场景?
标准库中 std::shared_mutex(C++17 起)允许多个读者同时访问,但写者独占。对生产者消费者模型来说,**它基本没用**:因为生产者和消费者都要修改队列(push/pop 都是写操作),不存在“只读”的消费者。所以仍得用独占锁 std::mutex。
除非你实现的是“只读广播”模型(比如日志分发,消费者只看不取),否则别被名字误导。强行套 shared_mutex 可能导致逻辑错误或死锁。
真正值得考虑的替代是无锁队列(如 moodycamel::ConcurrentQueue),但它引入第三方依赖,且调试难度陡增——多数场景下,带条件变量的互斥锁已足够高效且可控。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











