简介:
System.Threading.Interlocked 是 C# 中用于实现原子操作(Atomic Operations)的核心类。它提供了一组静态方法,确保在多线程环境下对共享变量的操作是线程安全的,且无需使用传统的锁机制(如 lock 或 Monitor),从而在高并发场景下具有更高的性能。
使用:
| 方法 | 描述 | 示例 |
|---|---|---|
| Increment | 原子地将指定变量加 1,并返回新值。 | Interlocked.Increment(ref counter); |
| Decrement | 原子地将指定变量减 1,并返回新值。 | Interlocked.Decrement(ref counter); |
| Add | 原子地将指定变量加上给定值,并返回新值。 | Interlocked.Add(ref counter, 5); |
| Exchange | 原子地将变量设置为新值,并返回原始值。 | int old = Interlocked.Exchange(ref value, 10); |
| CompareExchange | 比较并交换。如果当前值等于预期值,则替换为新值;否则不操作。始终返回原始值。 | Interlocked.CompareExchange(ref value, newValue, expectedValue); |
示例:
private static int _counter = 0;
static async Task Main(string[] args)
{
var tasks = new Task;
// 启动10个线程,每个线程增加1000次
for (int i = 0; i < 10; i++)
{
tasks[i] = Task.Run(() =>
{
for (int j = 0; j < 1000; j++)
{
// 原子递增,保证最终结果一定是 10000
Interlocked.Increment(ref _counter);
}
});
}
await Task.WhenAll(tasks);
Console.WriteLine($"Final Counter: {_counter}"); // 输出: Final Counter: 10000
}
与传统lock区别:
| 维度 | Interlocked | lock (Monitor) |
|---|---|---|
| 操作对象 | 单个基本类型变量 (int, long, reference) | 任意代码块、对象、集合、多个变量 |
| 典型场景 | 计数器、状态标志位 (0/1)、CAS 无锁算法 | 保护集合 (List, Dictionary)、事务性业务逻辑、文件IO |
| 性能 | 极高 (CPU 指令级,纳秒级) | 中等 (涉及内核对象,微秒/毫秒级) |
| 阻塞行为 | 不阻塞线程,失败即重试或返回 | 阻塞线程,直到获取锁 |
| 死锁风险 | 无 | 有 (若嵌套使用不当) |
注意事项:
private static int counter = 0;
Interlocked.Increment(ref counter); // 写入 1
Interlocked.Decrement(ref counter); // 写入 0
// 不要这么用:counter是一个普通的 int 字段,在多线程环境下读取不是原子的,不保证内存可见性
if (counter > 0)
{
Debug.WriteLine("业务逻辑1");
}
else
{
Debug.WriteLine("业务逻辑2");
}
// 正确用法:永远使用 Interlocked 保证内存可见性
if (Interlocked.CompareExchange(ref counter, 0, 0) > 0)
{
Debug.WriteLine("业务逻辑1");
}
else
{
Debug.WriteLine("业务逻辑2");
}
拓展
锁(lock) 和信号量(Semaphore/SemaphoreSlim)区别:
锁(lock):计数固定为 1。同一时刻,只有一个线程能进入被保护的代码块。其他线程必须排队等待。是线程相关的,它要求同一个线程必须负责释放锁(退出 lock 块)。
信号量(Semaphore):计数可以设为 任意正整数(如 3)。它允许最多 N 个 线程同时进入,如果已有 N 个线程在占用,第 N+1 个线程就必须等待。是计数相关的。它不追踪线程 ID。任何一个线程都可以调用 Release() 来增加信号量的计数,即使这个线程之前并没有调用过 WaitOne()。