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 类

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

文章插图

C# Interlocked 类

文章插图
在这里将变量添加

经验总结扩展阅读