Interlocked是C#无锁编程最常用起点,仅适用于单变量原子读改写;Increment适合高频计数器场景,CompareExchange是唯一CAS原语,需循环重试,不支持浮点、复合逻辑或多字段同步。

直接说结论:Interlocked 是 C# 无锁编程最常用、最安全的起点,但它不是万能的“无锁开关”——它只解决特定类型的原子读改写,用错场景反而引入更隐蔽的 bug。
什么时候必须用 Interlocked.Increment 而不是 lock
典型场景就是高频计数器:请求量统计、缓存命中次数、对象创建总数。这类操作只涉及一个 int 或 long 字段的加一,且不需要和其他变量联动。
-
count必须是类字段(不能是属性、局部变量或只读字段),否则编译报错CS0208: Cannot take the address of a managed object - 返回值是递增后的值,可直接用于限流判断:
if (Interlocked.Increment(ref reqCount) > 100) Reject(); - 性能上比
lock高一个数量级:没有线程挂起/唤醒开销,不触发上下文切换 - 错误写法:
Interlocked.Increment(ref this.Count)(Count是自动属性)→ 属性背后是 get/set 方法,无法取地址
Interlocked.CompareExchange 是唯一能模拟 CAS 的方法
当你需要“先读再判断再写”的原子语义时,比如懒初始化、状态翻转、无锁栈 push/pop,就必须靠 CompareExchange + 循环重试。
- 签名是
CompareExchange(ref T location, T value, T comparand),它只在location == comparand时才把value写入,并返回location的原始值 - 单次调用不保证成功:失败后必须重新读取最新值,再尝试——这是无锁结构的标配模式,不是 bug
- 严禁传入表达式:
Interlocked.CompareExchange(ref x, x + 1, x)是错的,因为两次x求值之间可能被其他线程修改 - 常见误用:用它保护多个字段(如同时更新
sum和count)→CompareExchange只能原子更新一个内存位置
Interlocked 不支持的操作,踩坑最多
它不是“轻量版 lock”,而是硬件原子指令的封装,能力边界非常明确。
- 不支持浮点类型:
Interlocked.Add(ref double x, double value)不存在,.NET 没有对应 CPU 指令 - 不支持乘除、位运算等复合操作:只有
Increment、Add、And、Or、Xor、CompareExchange等有限几个 - 对引用类型(
class)只保证指针赋值原子,不保证对象内部状态线程安全;若对象本身被多线程修改,仍需额外同步 - 结构体(
struct)不能直接用CompareExchange,除非是ref struct且字段布局可控,否则会因 GC 移动导致指针失效
为什么别急着手写无锁队列或链表
绝大多数业务场景下,ConcurrentQueue<T> 和 ConcurrentStack<T> 就是正确答案。它们底层已用 Interlocked + volatile + 循环重试实现 Michael-Scott 算法,但你几乎不会遇到 ABA、内存重排序、节点回收泄漏这些底层问题。
- 手写无锁队列的硬伤:需要双指针协同(head/tail),而 .NET 缺乏原生双字 CAS(
Interlocked.CompareExchange128仅 Windows x64 支持,且不适用于托管引用) - GC 干预不可控:对象被回收后,旧指针可能被复用,引发 ABA;手动管理内存需
unsafe+stackalloc+ 显式生命周期控制 - 调试成本极高:问题只在高并发下偶发,日志里看不到线索,Core Dump 也难定位
- 性能未必更好:.NET Core 3.0+ 的
ConcurrentQueue<T>已深度优化,自研实现很难超越其吞吐量
真正该自己动手的,往往只是 SPSC 场景下的环形缓冲区,或者极简的计数器/标志位——其余时候,信任 BCL 提供的并发集合,比炫技更重要。



















