文章插图
可以看到这个函数没有 32 位(int)类型的重载,为什么要单独为 64 位的 long/ulong 类型单独提供原子性读取操作符呢?
这是因为CPU有 32 位处理器和 64 位处理器,在 64 位处理器上,寄存器一次处理的数据宽度是 64 位,因此在 64 位处理器和 64 位操作系统上运行的程序 , 可以一次性读取 64 位数值 。
但是在 32 位处理器和 32 位操作系统情况下,long/ulong 这种数值,则要分成两步操作来进行 , 分别读取 32 位数据后,再合并在一起,那显然就会出现多线程情况下的并发问题 。
因此这里提供了原子性的方法来应对这种情况 。
文章插图
这里底层同样用了
CompareExchange
操作来保证原子性 , 参数这里就给了两个0,可以兼容如果原值是 0 则写入 0,如果原值非 0 则不写入 , 返回原值 。__sync_val_compare_and_swap 函数在写入新值之前,读出旧值,当且仅当旧值与存储中的当前值一致时,才把新值写入存储【关于性能】多线程下实现原子性操作方式有很多种,我们一定会关心在不同场景下 , 不同方法间的性能问题,那么我们简单来对比下
Interlocked
类提供的方法和 lock
关键字的性能对比我们同样用线程池调度50个Task(内部可能线程重用),分别执行 200000 次自增运算
public static void IncreamentPerformance(){//lock methodvar locker = new object();var stopwatch = new Stopwatch();stopwatch.Start();var j1 = 0;Task.WaitAll(Enumerable.Range(0, 50).Select(t =>Task.Run(() =>{for (int i = 0; i < 200000; i++){lock (locker){j1++;}}})).ToArray());Console.WriteLine($"Monitor lock,result={j1},elapsed={stopwatch.ElapsedMilliseconds}");stopwatch.Restart();//Increment methodvar j2 = 0;Task.WaitAll(Enumerable.Range(0, 50).Select(t =>Task.Run(() =>{for (int i = 0; i < 200000; i++){Interlocked.Increment(ref j2);}})).ToArray());stopwatch.Stop();Console.WriteLine($"Interlocked.Increment , result={j2},elapsed={stopwatch.ElapsedMilliseconds}");}
运算结果文章插图
可以看到,采用
Interlocked
类中的自增函数,性能比 lock
方式要好一些虽然这里看起来性能要好,但是不同的业务场景要针对性思考,采用恰当的编码方式,不要一味追求性能我们简单分析下造成执行时间差异的原因
我们都知道,使用lock(底层是Monitor类),在上述代码中会阻塞线程执行,保证同一时刻只能有一个线程执行
j1++
操作,因此能保证操作的原子性 , 那么在多核CPU下,也只能有一个CPU核心在执行这段逻辑 , 其他核心都会等待或执行其他事件,线程阻塞后,并不会一直在这里傻等,而是由操作系统调度执行其他任务 。由此带来的代价可能是频繁的线程上下文切换,并且CPU使用率不会太高,我们可以用分析工具来印证下 。Visual Studio 自带的分析工具 , 查看线程使用率
文章插图
使用 Process Explorer 工具查看代码执行过程中上下文切换数
文章插图
文章插图
可以大概估计出 , 采用 lock(Monitor)同步自增方式,上下文切换
243
次那么我们用同样的方式看下底层用
CAS
函数执行自增的开销Visual Studio 自带的分析工具,查看线程使用率
文章插图
使用 Process Explorer 工具查看代码执行过程中上下文切换数
文章插图
文章插图
可以大概估计出,采用
CAS
自增方式,上下文切换 220
次可见,不论使用什么技术手段,线程创建太多都会带来大量的线程上下文切换
这个应该是和测试的代码相关两者比较大的区别在CPU的使用率上,因为 lock 方式会造成线程阻塞,因此不会所有的CPU核心同时参与运算 , CPU在当前进程上使用率不会太高,但 cas 方式CPU在自己的时间分片内并没有被阻塞或重新调度 , 而是不停地执行比较替换的动作(其实这种场景算是无用功,不必要的负开销),造成CPU使用率非常高 。
推荐阅读
- 无期迷途角色重置方法
- 怎么练轻功儿童(普通人怎么练飞行术)
- 在家怎么练轻功(怎样练轻功在空中飞)
- 20_Vue如何监测数组类型数据发生改变的?
- 二 【单元测试】Junit 4--eclipse配置Junit+Junit基础注解
- oppofindx3pro跑分_oppofindx3pro安兔兔跑分
- 只知道微信昵称删除了对方怎么找对方(彻底删除对方微信)
- 19_Vue如何监测到对象类型数据发生改变的?
- 三、Ocelot请求聚合与负载均衡
- 荣耀X30max手机参数_荣耀X30max详细配置