concurrentqueue 并非纯无锁,其在扩容 segment 和首次初始化 head/tail 时使用 interlocked.compareexchange 配合自旋回退,属 lock-free 但非 wait-free;核心通过分段设计与原子指令避免锁竞争,不使用 monitor.enter 或 lock。
为什么 ConcurrentQueue<t></t> 不是纯无锁但表现接近
很多人以为 ConcurrentQueue<t></t> 是完全 lock-free 的,其实它内部在**扩容 segment** 和**首次初始化 head/tail 节点**时仍会用到 Interlocked.CompareExchange 配合少量自旋+回退逻辑,严格来说属于「无锁(lock-free)但非等待无关(wait-free)」。它的核心设计是把竞争拆到多个独立 segment 上,让绝大多数入队/出队操作只依赖 Interlocked 原子指令完成,避免了 lock 语句带来的线程挂起开销。
关键点在于:它不使用 Monitor.Enter 或 lock(obj),所有共享状态更新都靠 Interlocked.Increment、Interlocked.CompareExchange 等实现——这是 lock-free 的底线。
ConcurrentQueue<t></t> 的 head/tail 分离与 ABA 问题规避
它用两个独立的 volatile 字段 _head 和 _tail 分别指向当前可消费/可插入的节点,避免单指针更新时的 ABA 冲突。每次 Enqueue 尝试用 Interlocked.CompareExchange 更新 _tail,失败就重试;Dequeue 同理操作 _head。但真正巧妙的是:它不直接修改节点的 Next 引用,而是先用 Interlocked.CompareExchange 把新节点挂到当前 tail 的 Next,再尝试推进 _tail —— 这样即使发生 ABA,也不会破坏链表结构。
- 节点
Next字段声明为volatile,确保可见性 - 每个 segment 固定大小(默认 32),满后原子切换到新 segment,避免长链表遍历
- 没有使用
Unsafe或指针,纯托管代码,兼容 GC 和跨平台运行时
自己写 lock-free 队列前必须面对的三个硬伤
手写生产级 lock-free 队列远比看起来危险。.NET 的内存模型、JIT 重排序、GC 移动对象都会悄悄破坏你的假设: