【前言】在日常开发工作中 , 我们经常要对变量进行操作,例如对一个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
方法来实现自增的
文章插图
再往下看,
Add
方法是通过 ExchangeAdd
方法来实现原子性的自增,因为该方法返回值是增加前的原值,因此返回时增加了本次新增的 , 结果便是相加的结果 , 当然 location1
变量已经递增成功了,这里只是为了友好地返回增加后的结果 。文章插图
我们再往下看
文章插图
这个方法用
[MethodImpl(MethodImplOptions.InternalCall)]
修饰,表明这里调用的是 CLR 内部代码,我们只能通过查看源码来继续学习 。我们打开 dotnetcore 源码:https://github.com/dotnet/corefx
找到
Interlocked
中的 ExchangeAdd
方法文章插图
可以看到 , 该方法用循环不断自旋赋值并检查是否赋值成功(CompareExchange返回的是修改前的值,如果返回结果和修改前结果是一致,则说明修改成功)
我们继续看内部实现
文章插图
【C# Interlocked 类】
文章插图
内部调用
InterlockedCompareExchange
函数,再往下就是直接调用的C++源码了文章插图
文章插图
在这里将变量添加
volatile
修饰符,阻止寄存器缓存变量值(关于volatile不在此赘述) , 然后直接调用了C++底层内部函数 __sync_val_compare_and_swap
实现原子性的比较交换操作,这里直接用的是 CPU 指令进行原子性操作,性能非常高 。相同机制函数和
Increment
函数机制类似,Interlocked
类下的大部分方法都是通过 CompareExchange
底层函数来操作的 , 因此这里不再赘述- Add 添加值
- CompareExchange 比较交换
- Decrement 自减
- Exchange 交换
- And 按位与
- Or 按位或
- Read 读64位数值
推荐阅读
- 无期迷途角色重置方法
- 怎么练轻功儿童(普通人怎么练飞行术)
- 在家怎么练轻功(怎样练轻功在空中飞)
- 20_Vue如何监测数组类型数据发生改变的?
- 二 【单元测试】Junit 4--eclipse配置Junit+Junit基础注解
- oppofindx3pro跑分_oppofindx3pro安兔兔跑分
- 只知道微信昵称删除了对方怎么找对方(彻底删除对方微信)
- 19_Vue如何监测到对象类型数据发生改变的?
- 三、Ocelot请求聚合与负载均衡
- 荣耀X30max手机参数_荣耀X30max详细配置