C# Interlocked 类

【前言】在日常开发工作中 , 我们经常要对变量进行操作,例如对一个int变量递增++ 。在单线程环境下是没有问题的,但是如果一个变量被多个线程操作,那就有可能出现结果和预期不一致的问题 。
例如:
static void Main(string[] args){var j = 0;for (int i = 0; i < 100; i++){j++;}Console.WriteLine(j);//100}在单线程情况下执行 , 结果一定为100,那么在多线程情况下呢?
static void Main(string[] args){var j = 0;var t1 = Task.Run(() =>{for (int i = 0; i < 50000; i++){j++;}});var t2 = Task.Run(() =>{for (int i = 0; i < 50000; i++){j++;}});Task.WaitAll(t1, t2);Console.WriteLine(j);//82869 这个结果是随机的,和每个线程执行情况有关}我们可以看到,多线程情况下并不能保证执行正确,我们也将这种情况称为 “非线程安全”
这种情况下我们可以通过加锁来达到线程安全的目的
static void Main(string[] args){var locker = new object();var j = 0;var t1 = Task.Run(() =>{for (int i = 0; i < 50000; i++){lock (locker){j++;}}});var t2 = Task.Run(() =>{for (int i = 0; i < 50000; i++){lock (locker){j++;}}});Task.WaitAll(t1, t2);Console.WriteLine(j);//100000 这里是一定的}加锁的确能解决上述问题 , 那么有没有一种更加轻量级 , 更加简洁的写法呢?
那么 , 今天我们就来认识一下 Interlocked 类
【Interlocked 类下的方法】Increment(ref int location)Increment 方法可以轻松实现线程安全的变量自增
/// <summary>/// thread safe increament/// </summary>public static void Increament(){var j = 0;Task.WaitAll(Enumerable.Range(0, 50).Select(t =>Task.Run(() =>{for (int i = 0; i < 2000; i++){Interlocked.Increment(ref j);}})).ToArray());Console.WriteLine($"multi thread increament result={j}");//result=100000}看到这里,我们一定好奇这个方法底层是怎么实现的?
我们通过ILSpy反编译查看源码:
首先看到 Increment 方法其实是通过调用 Add 方法来实现自增的

C# Interlocked 类

文章插图
再往下看,Add 方法是通过 ExchangeAdd 方法来实现原子性的自增,因为该方法返回值是增加前的原值,因此返回时增加了本次新增的 , 结果便是相加的结果 , 当然 location1 变量已经递增成功了,这里只是为了友好地返回增加后的结果 。
C# Interlocked 类

文章插图
我们再往下看
C# Interlocked 类

文章插图
这个方法用 [MethodImpl(MethodImplOptions.InternalCall)] 修饰,表明这里调用的是 CLR 内部代码,我们只能通过查看源码来继续学习 。
我们打开 dotnetcore 源码:https://github.com/dotnet/corefx
找到 Interlocked 中的 ExchangeAdd 方法
C# Interlocked 类

文章插图
可以看到 , 该方法用循环不断自旋赋值并检查是否赋值成功(CompareExchange返回的是修改前的值,如果返回结果和修改前结果是一致,则说明修改成功)
我们继续看内部实现
C# Interlocked 类

文章插图
【C# Interlocked 类】
C# Interlocked 类

文章插图
内部调用 InterlockedCompareExchange 函数,再往下就是直接调用的C++源码了
C# Interlocked 类

文章插图

C# Interlocked 类

文章插图
在这里将变量添加 volatile 修饰符,阻止寄存器缓存变量值(关于volatile不在此赘述) , 然后直接调用了C++底层内部函数 __sync_val_compare_and_swap 实现原子性的比较交换操作,这里直接用的是 CPU 指令进行原子性操作,性能非常高 。
相同机制函数和 Increment 函数机制类似,Interlocked 类下的大部分方法都是通过 CompareExchange 底层函数来操作的 , 因此这里不再赘述
  • Add 添加值
  • CompareExchange 比较交换
  • Decrement 自减
  • Exchange 交换
  • And 按位与
  • Or 按位或
  • Read 读64位数值
public static long Read(ref long location)Read 这个函数着重提一下

推荐阅读