Interlocked专治单个变量的原子读写,lock用于多步逻辑的排他执行;前者无锁高效,后者支持复杂临界区,误用会导致性能下降或并发错误。

什么时候该用 Interlocked,而不是 lock
直接说结论:Interlocked 专治「单个变量的原子读写」,比如计数器增减、标志位设置、引用替换;lock 是为「一段逻辑的排他执行」准备的,比如更新多个字段、读-改-写复合操作、涉及 I/O 或复杂校验的临界区。
常见错误现象:有人把 Interlocked.Increment(ref _count) 换成 lock 去保护一个 _count++,结果性能掉一截还毫无必要;也有人试图用 Interlocked 去保证「先检查余额再扣款」这种两步操作的原子性,结果出现超卖——因为 Interlocked 不提供「条件+动作」的原子组合能力。
-
Interlocked底层靠 CPU 原子指令(如LOCK XADD),无锁、无上下文切换、无等待,是真正的「无锁编程」基础 -
lock底层调用Monitor.Enter/Exit,会触发内核态同步原语,线程争抢失败时可能挂起、调度,开销明显更高 - 只要操作不超出「一个变量 + 一种原子语义」,就优先选
Interlocked;一旦涉及多个变量、判断分支、非原子表达式(如x = x * 2 + 1),lock或Monitor就不可替代
Interlocked.CompareExchange 是无锁编程的核心入口
C# 的无锁编程不是靠“不用锁”来定义的,而是靠「CAS(Compare-And-Swap)循环重试」实现的乐观并发控制。而 Interlocked.CompareExchange 就是这个机制的唯一公开出口。
它签名是:Interlocked.CompareExchange(ref int location1, int value, int comparand) —— 只有当 location1 == comparand 时,才把 value 写入,并返回旧值;否则不写,只返回当前值。整个过程原子。
- 典型用法是自旋写入:反复读取当前值 → 计算新值 → CAS 尝试更新 → 失败则重试
- 不能用于任意对象:只能用于
int、long、IntPtr、引用类型(object或泛型Twhere T : class) - 注意内存屏障:
CompareExchange默认带 full memory barrier,但若需更细粒度(如仅 acquire/release),得用Interlocked.CompareExchange(.NET 8+)(ref T, T, T, MemoryOrder)
private int _state = 0; // 0=ready, 1=busy
public bool TryEnter()
{
return Interlocked.CompareExchange(ref _state, 1, 0) == 0;
}
// 成功返回 true,且 _state 已设为 1;失败说明已被别人抢先设为 1
lock 和 Interlocked 性能差距有多大?
在高竞争、高频更新场景下,差距非常真实:10 个线程对同一计数器做 100 万次递增,Interlocked.Increment 通常比 lock 快 3–5 倍,且 CPU 时间更稳,不会因线程挂起/唤醒抖动。
但这个优势只在「简单操作」上成立。一旦你把 Interlocked 套进复杂逻辑里强行“无锁”,比如在 CAS 循环里调用数据库、做字符串拼接、访问非原子字段,性能反而更差——因为自旋浪费 CPU,且逻辑本身已失去原子性保障。
- 别为了“无锁”而无锁:无锁 ≠ 更快,而是「适合场景时更轻量」
-
Interlocked操作本身不会阻塞,但你的业务逻辑如果包含阻塞点(如 await、File.Read、Thread.Sleep),那整个方案就不再是无锁了 - 真要压榨性能,可搭配
SpinLock(短临界区)、ReaderWriterLockSlim(读多写少)、或Channels(生产者-消费者解耦)
最容易被忽略的坑:Interlocked 不保顺序,也不保可见性之外的语义
很多人以为用了 Interlocked 就万事大吉,结果发现日志乱序、状态跳变、甚至偶发 null 引用——问题往往出在「它只管那个变量本身,不管其他东西」。
-
Interlocked.Exchange(ref obj, newObj)确保引用替换原子,但不保证newObj的构造已完成(即可能看到部分初始化的对象),除非你在构造后才交换 - 多个
Interlocked调用之间**没有顺序保证**:A 线程执行Interlocked.Increment(ref x)和Interlocked.Increment(ref y),B 线程可能看到y先变、x后变 - 它不阻止编译器/CPU 重排序:若需严格顺序(比如先写数据、再设就绪标志),得配合
Thread.MemoryBarrier()或用volatile字段(但volatile和Interlocked混用需格外小心)
真正难的从来不是“怎么写无锁”,而是“怎么证明它在所有路径下都正确”。哪怕只多一步判断,就很可能得退回 lock。











