C#的并发机制优秀在哪?( 三 )


文章图片

因此我们可以基本得出 this.Invoke(new DeleteDelegate(DeleteFileHandler), new object[] { sender,e }); ;这行看似无关紧要的代码之后 , 无意中使files共享变量的维护操作 , 由多核多线程共同操作 , 变成了众多子线程向主线程通信 , 所有维护操作均由主线程进行 , 这也使最终的执行效率有所提高 。
深度解读 , 为何要加两把锁
在当前使用通信替代共享内存的大潮之下 , 锁其实是最重要的设计 。
我们看到在.Net的Invoke实现中 , 使用了两把锁lock (this) 与lock (threadCallbackList) 。

lock( this) {if(threadCallbackList == null) {threadCallbackList = newQueue;} } lock(threadCallbackList) {if(threadCallbackMessage == 0) {threadCallbackMessage = SafeNativeMethods.RegisterWindowMessage(Application.WindowMessagesVersion + "_ThreadCallbackMessage");} threadCallbackList.Enqueue(tme); } 在.NET当中lock关键字的基本可以理解为提供了一个近似于CAS的锁(Compare And Swap) 。 CAS的原理不断地把"期望值"和"实际值"进行比较 , 当它们相等时 , 说明持有锁的CPU已经释放了该锁 , 那么试图获取这把锁的CPU就会尝试将"new"的值(0)写入"p"(交换) , 以表明自己成为spinlock新的owner 。 伪代码演示如下:
voidCAS( intp, intold, intnew){ if*p != olddonothingelse *p ← new } 基于CAS的锁效率没问题 , 尤其是在没有多核竞争的情况CAS表现得尤其优秀 , 但CAS最大的问题就是不公平 , 因为如果有多个CPU同时在申请一把锁 , 那么刚刚释放锁的CPU极可能在下一轮的竞争中获取优势 , 再次获得这把锁 , 这样的结果就是一个CPU忙死 , 而其它CPU却很闲 , 我们很多时候诟病多核SOC“一核有难 , 八核围观”其实很多时候都是由这种不公平造成的 。
为了解决CAS的不公平问题 , 业界大神们又引入了TAS(Test And Set Lock)机制 , 个人感觉还是把TAS中的T理解为Ticket更好记一些 , TAS方案中维护了一个请求该锁的头尾索引值 , 由"head"和"tail"两个索引组成 。
structlockStruct{ int32head; int32tail; }; "head"代表请求队列的头部 , "tail"代表请求队列的尾部 , 其初始值都为0 。
最一开始时 , 第一个申请的CPU发现该队列的tail值是0,那么这个CPU会直接获取这把锁 , 并会把tail值更新为1 , 并在释放该锁时将head值更新为1 。
在一般情况下当锁被持有的CPU释放时 , 该队列的head值会被加1 , 当其他CPU在试图获取这个锁时 , 锁的tail值获取到 , 然后把这个tail值加1 , 并存储在自己专属的寄存器当中 , 然后再把更新后的tail值更新到队列的tail当中 。 接下来就是不断地循环比较 , 判断该锁当前的"head"值 , 是否和自己存储在寄存器中的"tail"值相等 , 相等时则代表成功获得该锁 。
TAS这类似于用户到政务大厅去办事时 , 首先要在叫号机取号 , 当工作人员广播叫到的号码与你手中的号码一致时 , 你就获取了办事柜台的所有权 。
但是TAS却存在一定的效率问题 , 根据我们上文介绍的MESI协议 , 这个lock的头尾索引其实是在各个CPU之间共享的 , 因此tail和head频繁更新 , 还是会引发调整缓存不停的invalidate , 这会极大的影响效率 。
因此我们看到在.Net的实现中干脆就直接引入了threadCallbackList的队列 , 并不断将tme(ThreadMethodEntry)加入队尾 , 而接收消息的进程 , 则不断从队首获取消息 。
C#的并发机制优秀在哪?

特别声明:本站内容均来自网友提供或互联网,仅供参考,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。