绝大多数项目不该手写lock-free queue,因其涉及内存序、aba问题、生命周期管理等复杂约束,易在高争用下暴露隐蔽缺陷;推荐使用经工业验证的moodycamel::concurrentqueue,或低吞吐场景直接用std::queue+mutex。

为什么直接手写 lock-free queue 是高风险操作
绝大多数项目不需要、也不该自己实现 lock-free queue。它不是“加个 std::atomic 就行”的事情,而是涉及内存序(memory_order)、ABA 问题、节点生命周期管理、缓存行对齐、甚至硬件原子指令边界等一整套协同约束。真实场景中,一个看似工作的实现,在多核高争用下可能在几小时后才暴露出数据丢失或死锁——而且难以复现。
除非你:正在开发高性能基础设施(如自研消息中间件)、有多年并发编程经验、且已用 ThreadSanitizer + TSAN + herbgrind 等工具做过严格验证,否则请跳过手写环节。
用 moodycamel::ConcurrentQueue 替代手写的现实路径
这是目前 C++ 生态中最成熟、被广泛验证的无锁队列实现,支持单/多生产者、单/多消费者任意组合,底层用 std::atomic + memory_order 组合规避 ABA,并内置内存回收机制(BlockPool)。它不是“玩具”,而是被 folly、Seastar 等项目实际采用的组件。
- 安装只需复制头文件(
concurrentqueue.h),无依赖 - 构造时指定是否启用
explicit memory ordering(默认安全) - 注意:不提供
size()的强一致性保证——返回的是近似值,用于监控可以,用于逻辑判断会出错 - 避免在析构期间仍有线程调用
enqueue()或dequeue(),否则触发未定义行为
std::queue + std::mutex 什么时候反而更合适
如果你的队列吞吐量不高(比如每秒 std::queue 加一把 std::mutex 不仅代码清晰,性能也未必差——现代 mutex 在无争用时开销极低(futex 快路径),且调试、堆栈追踪、死锁检测都远比 lock-free 友好。
典型误判场景:
- 看到 “lock-free” 就以为“一定更快” → 实际在低并发下,mutex 的 cache locality 更优
- 认为“用了 atomic 就是 lock-free” → 单个
std::atomic<int></int>是 lock-free,但整个队列结构不是 - 忽略内存回收:无锁结构中被
dequeue的节点不能立即delete,必须等所有线程确认不再访问它——这需要Hazard Pointer或RCU等机制,不是加个shared_ptr就能解决
真要手写,从 Michael-Scott 队列开始理解边界
学术上最常被引用的无锁队列模型是 Michael-Scott(1998),它只支持单生产者/单消费者(SPSC),且假设指针宽度足够存放 tag(用于 ABA 防御)。它的核心是两个原子指针:head 和 tail,通过 CAS 循环推进。
但即使这个“最简”版本,也有几个硬伤必须处理:
-
tail指针推进时需检查是否已到达尾节点的next,否则会跳过新节点 - 节点释放必须延迟:哪怕只用
std::atomic_thread_fence,也得确保其他线程不会解引用已释放内存 - x86 下
memory_order_relaxed可能被重排,至少要memory_order_acquire/memory_order_release配对 - ARM/AArch64 对
memory_order_seq_cst的代价更高,不能无脑照搬 x86 写法
真正上线前,必须在目标 CPU 架构上跑满 72 小时以上压力测试,并用 libatrace 或 perf record -e mem-loads,mem-stores 观察 false sharing 是否发生——而这点,连很多开源实现都疏忽了。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











