Interlocked.CompareExchange 通过 CPU 硬件级原子指令(如 x86 的 CMPXCHG)实现不可分割的“读取→比较→写入”,返回原始值以供判断成败,失败时不写入、不抛异常;典型用于自旋等待、状态机切换和一次性初始化。

Interlocked.CompareExchange 是怎么实现“比较后交换”的
它不是简单的 if-else,而是一条 CPU 硬件级原子指令(如 x86 的 CMPXCHG),整个“读取→比较→写入”三步不可分割。哪怕两个线程同时执行 Interlocked.CompareExchange(ref _state, 1, 0),也只会有一个成功把 _state 从 0 改成 1,另一个拿到旧值 0 后直接返回,不修改内存。
- 返回值永远是
location的**原始值**(不是布尔结果),这是判断是否成功的唯一依据 - 操作失败时**不做任何写入**,也不会抛异常,必须靠返回值主动判断
- 对浮点数(
float/double)使用需谨慎:IEEE 754 的 NaN、±0 等特殊值会导致意外的比较失败 - 泛型重载
CompareExchange只比较**引用相等性**((ref T, T, T) ReferenceEquals),不是Equals()或内容相等
常见用法:自旋等待、无锁计数器、状态机切换
它最典型的模式是“循环重试”,也就是 CAS 自旋(spin loop)。比如等待某个整型标志位从 expected 变成 desired:
while (Interlocked.CompareExchange(ref _ready, 1, 0) == 0)
{
// 还没就绪,继续等(可加 Thread.SpinWait 或小延时防空转)
}- 用在初始化一次性资源时很安全:只有第一个线程能把
_initialized从 0 改成 1,其余全部失败并跳过构造逻辑 - 实现无锁计数器时,不能只靠
CompareExchange,得配合Increment或手动循环更新(例如:读当前值 → 计算新值 → CAS 尝试写入 → 失败则重读再试) - 不要用它替代
Interlocked.Increment做简单累加——后者底层也是 CAS,但封装了重试逻辑,更简洁且不易出错
容易踩的坑:ABA 问题、内存顺序、类型混淆
CompareExchange 本身不解决 ABA 问题:假设变量从 A→B→A,CAS 会误认为“始终是 A”而成功交换,但中间状态已被篡改。C# 中没有内置带版本号的 AtomicStampedReference,得自己用 long 高 32 位存版本号、低 32 位存值来模拟。
- 默认内存语义是
SeqCst(顺序一致),性能略低;高并发场景可考虑带_acq/_rel后缀的内部函数(需 unsafe + P/Invoke),但普通应用几乎用不到 - 误用
CompareExchange(ref int, long, int)会因类型不匹配编译失败;注意所有参数类型必须严格一致(包括有/无符号、位宽) - 对对象引用使用时,传入的
comparand必须是**同一实例引用**,哪怕两个对象内容完全一样,CompareExchange(ref obj, newObj, oldObj)也会失败
和 Interlocked.Exchange 的关键区别在哪
Exchange 是“无条件覆盖”,CompareExchange 是“有条件覆盖”。前者适合设置标志位、替换缓存对象;后者才是真正的 CAS 原语,用于构建更复杂的同步逻辑。
-
Exchange(ref _flag, true):不管原来是什么,一律设为true,返回旧值 -
CompareExchange(ref _flag, true, false):仅当原值是false才设为true,否则不动;返回值可用于判断是否首次设置 - 想实现“首次调用才执行某段逻辑”,用
CompareExchange;想实现“强制刷新最新值”,用Exchange
真正难的从来不是调用这行代码,而是想清楚:你要保护的到底是“一个值”,还是“一段状态变迁过程”。CAS 给你的是原子性,不是逻辑正确性——循环条件、重试策略、ABA 防御,都得自己兜底。









