基于循环数组的无锁队列通过cas原子操作实现线程安全,避免锁竞争与内存频繁分配;采用2的幂容量支持位运算索引,spsc场景可彻底无锁,mpmc则依赖两阶段cas抢位与内存序控制,兼顾性能与正确性。

用循环数组实现环形队列,核心是避免内存分配、减少同步开销,并通过原子操作或无锁设计(如 CAS)替代互斥锁。真正“低锁损耗”不等于完全无锁,而是把锁粒度压到最小——比如只在入队/出队的边界处加锁,或用双指针 + 内存序控制实现无锁(Lock-Free)版本。下面分关键点说明。
一、循环数组结构与索引计算
环形队列底层是固定长度的数组,用两个整数(head 和 tail)标记读写位置。关键不是取模(%),而是位运算加速:
- 要求数组长度为 2 的幂(如 1024、4096),这样
index & (capacity - 1)等价于index % capacity,且无分支、零延迟 - head 指向待读元素,tail 指向待写位置;空队列时 head == tail;满队列需预留一个空位(即 size = capacity − 1),或额外用 count 字段
- 判断是否满:`(tail + 1) & mask == head`(mask = capacity − 1);判断是否空:`head == tail`
二、单生产者单消费者(SPSC):可彻底无锁
这是最容易做到高性能的场景。因无竞争,head/tail 可分别由不同线程独占更新,只需用 memory_order_acquire/release 控制可见性:
- 生产者只改 tail,每次写入后用
std::atomic_thread_fence(memory_order_release)或直接对 tail 使用store(tail_new, memory_order_release) - 消费者只改 head,每次读取前用
load(memory_order_acquire)保证看到最新数据 - 无需锁,无 CAS 自旋,吞吐接近 memcpy 速度
三、多生产者多消费者(MPMC):用原子 CAS + 分离读写路径
竞争不可避免,但可通过设计降低锁争用:
- 避免全局锁:不用一个 mutex 保护整个队列,改用“两阶段提交”——先原子抢位(CAS tail),再填数据,最后确认提交(CAS 更新 tail 终值)
- 用
std::atomic<int>::compare_exchange_weak</int>实现无锁入队:循环尝试更新 tail,直到成功获得唯一写槽位 - 出队同理,但要注意 ABA 问题(可用带版本号的指针,或结合 hazard pointer);更实用的做法是引入“批处理”:一次 CAS 预留多个 slot,内部用普通数组下标管理,减少 CAS 频次
- 若必须用锁,优先选自旋锁(
std::atomic_flag)而非 mutex,尤其在预期等待极短时
四、内存布局与缓存友好优化
高频访问的 head/tail 和数据区若跨 cache line,会引发 false sharing,大幅降速:
- 将 head、tail、capacity、mask 等元数据各自 padding 到 64 字节边界(典型 cache line 大小)
- 数据数组本身连续分配,确保顺序访问不跨页、不缺页
- 生产者写数据时,尽量按 cache line 对齐批量填充(如一次写 8 个 int),减少 store buffer 压力
不复杂但容易忽略:无锁 ≠ 无脑用原子变量,而要理解内存模型;高性能 ≠ 追求绝对无锁,而是根据实际并发模型(SPSC/MPSC/MPMC)选择最简可行方案。工业级实现(如 boost::lockfree::queue、LMAX Disruptor)都基于这些原则做了深度调优。











