CAS是无锁队列插入的核心机制,通过硬件原子指令“比较并交换”协调多线程对共享指针的修改,以乐观策略避免阻塞;链表实现需原子维护tail和next字段,数组型则用CAS递增tail并验证槽位空闲。

CAS 是无锁队列插入操作的核心机制
节点插入不靠加锁排队,而是靠硬件级原子指令“比较并交换”(CAS)来协调多个线程对共享指针的修改。只要内存位置的当前值等于预期值,就原子地更新为新值;否则失败并重试。这种乐观策略避免了阻塞和上下文切换,在低到中等竞争下吞吐量明显优于锁队列。
链表实现中入队的关键步骤
以单向链表无锁队列为典型,插入需同时维护 tail 指针和节点的 next 字段,两者都必须用原子类型(如 std::atomic<node></node>)。常见高效做法包括:
- 先创建新节点,设置
next = nullptr - 循环读取当前
tail,再通过tail->next == nullptr判断它是否仍是队尾 - 若不是,则用 CAS 尝试将
tail“跳转”到真正尾节点(即推进 tail),再重试 - 若是,则用 CAS 将
tail->next由nullptr改为新节点地址 - 最后用 CAS 更新
tail指针指向新节点(注意:这步可延迟,不影响正确性,但影响后续插入效率)
避免 ABA 问题与虚假失败
当一个节点被出队、释放、又恰好被新分配复用时,tail 或 next 的指针值可能“看起来没变”,但语义已不同——这就是 ABA 问题。单纯 CAS 无法识别。解决方法包括:
- 使用带版本号的指针(如
std::atomic<uintptr_t>存储ptr | (version << 48)) - 在 C++ 中借助
std::atomic<std::shared_ptr<Node>>配合引用计数,天然规避释放后重用 - 对关键 CAS 使用
atomic_compare_exchange_weak配合循环重试,容忍短暂的伪失败
数组型无锁队列的插入优化
基于循环缓冲区(Ring Buffer)的无锁队列更适合固定大小场景。插入依赖两个原子整数:tail(写位置)和 head(读位置)。高效插入要点:
- 用 CAS 原子递增
tail获取独占写索引(类似 ticket lock 分配序号) - 检查该索引对应槽位是否空闲(可通过状态位或版本字段判断)
- 写入数据后,再用 CAS 标记该槽为“已就绪”,通知消费者可读
- 避免直接用
tail++,必须用fetch_add+ CAS 验证容量边界


















