interlocked 是 cpu 原子指令的封装,仅保障特定单内存位置的原子操作;不支持 double 加法、多字段同步、复合逻辑等,误用会掩盖竞态问题。

Interlocked 不是“线程安全的万能胶”,它是 CPU 原子指令的直译封装,只对特定操作提供硬件级保障;用错场景(比如想原子更新两个字段、或对 double 做加法)不仅无效,还会掩盖真正的问题。
为什么 Interlocked.Increment(ref x) 能避免数据竞争
因为 _counter++ 在底层展开为「读—改—写」三步,而多核 CPU 上两个线程可能同时读到旧值,导致结果丢失;Interlocked.Increment 对应一条带 LOCK 前缀的 XADD 指令(x86)或 LDAXR/STLXR 循环(ARM),由硬件保证整个操作不可分割。
关键点:
-
ref参数不是语法糖——它强制你传入变量的内存地址,否则编译器直接报错CS0208 - 返回值是操作后的值,可直接用于判断:
if (Interlocked.Increment(ref reqCount) > 100) Reject(); - 不适用于属性:
Interlocked.Increment(ref this.Count)❌,因为自动属性背后是 getter/setter 方法,无法取地址
CompareExchange 是唯一能做 CAS 的方法
Interlocked.CompareExchange 是 C# 中唯一暴露「比较并交换」语义的 API,所有无锁结构(栈、队列、懒初始化)都靠它 + 循环重试实现。
典型错误写法:
if (_state == 0) {
_state = 1; // ❌ 条件检查和赋值之间存在竞态窗口
}
正确写法:
int original = Interlocked.CompareExchange(ref _state, 1, 0);
if (original == 0) {
// 真正执行了交换,可以安全做后续初始化
}
注意:
- 必须用返回值判断是否成功,不能只看条件表达式
- 失败后要重读最新值再试,这是无锁编程的常态,不是 bug
- .NET 6+ 的泛型
CompareExchange<t></t>仅对不可变引用类型或纯值类型安全,结构体慎用
哪些操作 Interlocked 根本不支持
它的能力边界非常清晰,强行越界只会引入隐蔽 bug:
- 不支持浮点类型:
Interlocked.Add(ref double x, double v)❌,CPU 没有原生原子 double 加法指令 - 不支持复合逻辑:无法原子地「先加 5 再乘 2」,只能拆成两步,中间状态对其他线程可见
- 不支持多字段同步:不能保证
balance和lastUpdated同时更新且原子 - 对引用类型只保证指针赋值原子,不保护对象内部状态——
Interlocked.CompareExchange(ref obj, newObj, null)安全,但obj.Value++仍需额外同步
long 类型在 x86 和 x64 上的行为差异
Interlocked.Read(ref long)、Increment(ref long) 在不同平台下依赖不同指令:
- x64:64 位操作天然原子,
Read编译为单条MOV,极快 - x86:必须用
LOCK XCHG或LOCK CMPXCHG8B实现,开销明显高于 int 操作 - 因此,在 32 位环境或需要极致性能的场景,优先用
int计数,避免无谓的long
最常被忽略的一点:Interlocked 解决的是「单个内存位置的原子读改写」,它从不承诺可见性顺序。如果你需要确保其他变量的修改对其他线程及时可见,还得配合 Volatile.Read 或内存屏障,不能只靠它。











