直接手写无锁队列大概率出错,因其需深入理解atomic内存序、ABA问题、内存重排及指针宽原子性,稍有不慎即引发数据竞争或未定义行为;推荐使用boost::lockfree::queue或moodycamel::ConcurrentQueue等成熟实现。

为什么直接手写无锁队列大概率出错
因为 std::queue 不是线程安全的,而“无锁”不等于“去掉 mutex”,它要求所有操作(入队/出队)都用原子指令 + 内存序约束完成,稍有不慎就会触发 ABA 问题、内存重排、虚假失败或漏唤醒。主流项目几乎都不手写,而是用成熟实现——比如 boost::lockfree::queue 或 moodycamel::ConcurrentQueue。
用 moodycamel::ConcurrentQueue 替代手写的实操建议
这是目前 C++ 生态中最常用、最经得起压测的无锁队列,支持单生产者单消费者(SPSC)、多生产者单消费者(MPSC)等模式,且 API 接近标准容器。
- 头文件只需一个:
#include "concurrentqueue.h"(单头文件,无需编译) - 基本用法和
std::queue类似,但底层完全无锁:moodycamel::ConcurrentQueue<int> q; q.enqueue(42); int val; q.try_dequeue(val); // 返回 bool,成功才赋值</int>
- 注意:默认构造是 MPSC 模式;若确定是 SPSC 场景,可显式传参提升性能:
moodycamel::ConcurrentQueueq(moodycamel::ConcurrentQueue ::SingleProducer, moodycamel::ConcurrentQueue ::SingleConsumer); - 不支持迭代器、
size()是 O(1) 但非精确(因无锁下难以实时统计),别依赖它做循环判空
自己用 std::atomic 实现简易 SPSC 队列的关键点
仅限学习或极端受限场景(如嵌入式无第三方库)。核心是环形缓冲区 + 两个原子索引,必须严格配对使用 memory_order_acquire/memory_order_release。
- 入队时更新 tail,需用
fetch_add+memory_order_relaxed(因只被单线程修改);但读 head 时必须用load(memory_order_acquire),防止编译器/CPU 把读 head 的指令重排到读数据之前 - 出队同理:读 tail 用
load(memory_order_acquire),更新 head 用fetch_add+memory_order_release - ABA 问题在 SPSC 下天然不存在(只有一个生产者改 tail,一个消费者改 head),所以不用
atomic加版本号 - 务必用
std::hardware_destructive_interference_size对齐 head/tail 变量,避免伪共享(false sharing)
哪些情况其实不该上无锁队列
无锁 ≠ 更快。当队列长度短、操作频率低、或临界区本身很重(比如入队要序列化一个 protobuf),加锁开销远小于无锁带来的复杂性和调试成本。
- 典型误用:用无锁队列传大对象(如
std::vector),拷贝开销远超锁竞争,此时应传std::shared_ptr或 move-only 类型 - 调试困难:无锁逻辑出错往往表现为偶发丢数据、卡死、或 valgrind 报
Helgrind警告,但堆栈看不出明显问题 - 平台限制:某些旧 ARM 或 RISC-V 架构不支持
cmpxchg16b等指令,moodycamel会退化为有锁,需检查编译日志中是否出现MOODYCAMEL_QUEUE_INTERNAL_DEBUG相关提示
真正难的不是写几个 atomic_fetch_add,而是确认你的场景确实需要它,并能覆盖所有内存序边界条件——多数时候,先用 std::mutex + std::queue 压测到瓶颈,再换无锁更稳妥。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











