C# 无锁编程技术 C#如何使用Interlocked实现无锁算法

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

Interlocked 为什么能替代 lock

因为

Interlocked
系列方法(如
Increment
Add
CompareExchange
)在底层直接编译为 CPU 的原子指令(如 x86 的
XADD
CMPXCHG
),不需要操作系统介入线程调度或获取互斥锁,天然避免了上下文切换和锁竞争开销。它只适用于简单类型(
int
long
ref
bool
等)的单次读-改-写操作,不能用于复合逻辑(比如“先读再判断再写”这种多步操作,除非用
CompareExchange
手动实现 CAS 循环)。

什么时候该用 Interlocked.Increment 而不是 lock

典型场景是计数器累加——比如统计请求量、缓存命中次数、对象创建总数等纯数值递增需求。此时

Interlocked.Increment(ref count)
lock(obj) { count++; }
更轻量、无阻塞、无死锁风险。

count
必须是
int
long
类型的字段(不能是属性,也不能是局部变量,必须可寻址)
不要试图用它保护多个变量:比如同时更新
sum
count
,这无法原子保证一致性
返回值有意义:
Interlocked.Increment
返回的是**递增后的值**,可直接用于条件判断(如限流:if (Interlocked.Increment(ref reqCount) > 100) Reject())

用 Interlocked.CompareExchange 实现无锁栈或计数器条件更新

这是唯一能模拟“CAS(Compare-And-Swap)”行为的方法,适用于需要“读-判断-写”原子语义的场景。比如实现一个线程安全的懒初始化标志:

private int _initialized = 0; // 0=未初始化,1=已初始化
public void EnsureInitialized()
{
    if (Interlocked.CompareExchange(ref _initialized, 1, 0) == 0)
    {
        // 当前值是 0,成功设为 1,说明本线程首次执行初始化
        InitializeCore();
    }
}

关键点:

CompareExchange(ref location, newValue, comparand)
只有当
location == comparand
时才把
newValue
写入,并返回原始值;否则不写,只返回当前值
必须用循环重试才能实现真正可靠的无锁结构(如无锁队列),单次调用不等于“一定成功”,失败后需重新读取最新值再试 不能传入计算表达式作为参数(如
Interlocked.CompareExchange(ref x, x + 1, x)
是错的——
x
在两次求值间可能已被其他线程修改)

Interlocked 不支持哪些操作?常见误用陷阱

它不提供原子的乘法、除法、浮点运算,也不支持结构体或对象引用的深层比较(

CompareExchange<t></t>
只比较引用地址,不是内容)。以下写法都是危险或无效的:

double
decimal
调用
Interlocked.Add
—— 编译报错,.NET 不提供浮点原子操作
Interlocked.Exchange(ref obj, new MyObj())
替代锁来保护对象状态 —— 这只原子替换引用,但
MyObj
内部字段仍可能被多线程并发修改
foreach
遍历集合时用
Interlocked
修改集合长度字段 —— 集合本身不是线程安全的,仅改长度毫无意义,且可能破坏内部结构
Interlocked.Read(ref longValue)
用在非
long
字段上 —— 它只接受
ref long
,且仅在 32 位系统上对
long
读取有必要(64 位系统上普通读已是原子)

真正的无锁编程难点不在调用几个

Interlocked
方法,而在于设计出能用有限原子原语表达的正确并发逻辑——多数业务场景下,老实用
lock
反而更安全、更易维护。

相关推荐