c# Interlocked.CompareExchange 的用法和CAS原子操作原理

来源:这里教程网 时间:2026-02-21 17:37:59 作者:

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<t>(ref T, 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 防御,都得自己兜底。

相关推荐