我的编程空间,编程开发者的网络收藏夹
学习永远不晚

C#的并发机制有什么优势

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

C#的并发机制有什么优势

这篇文章主要介绍了C#的并发机制有什么优势,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。

一行没用的代码却提高了效率?

由于我需要记录的文件拷出信息并没有回显在UI的需要,因此也就没考虑并发冲突的问题,在最初版本的实现中,我对于filesystemwatcher的回调事件,都是直接处理的,如下:

private void DeleteFileHandler(object sender, FileSystemEventArgs e)        {            if(files.Contains(e.FullPath))            {                files.Remove(e.FullPath);               //一些其它操作            }        }

这个程序的处理效率在普通的办公PC上如果同时拷出20个文件,那么在拷贝过程中,U盘监测程序的CPU使用率大约是0.7%。

C#的并发机制有什么优势

但是一个非常偶然的机会,我使用了Event/Delegate的Invoke机制,结果发现这样一个看似的废操作,却让程序的CPU占用率下降到0.2%左右

 private void UdiskWather_Deleted(object sender, FileSystemEventArgs e)        {            if(this.InvokeRequired)            {                this.Invoke(new DeleteDelegate(DeleteFileHandler), new object[] { sender,e });               }            else            {                DeleteFileHandler(sender, e);            }        }

C#的并发机制有什么优势

在我最初的认识中.net中的Delegate机制在调用过程中是要进行拆、装箱操作的,因此这不拖慢操作就不错了,但实际的验证结果却相反。

 看似没用的Invoke到底有什么用

这里先给出结论,Invoke能提升程序执行效率,其关键还是在于线程在多核之间切换的消耗要远远高于拆、装箱的资源消耗,我们知道我们程序的核心就是操作files这个共享变量,每次在被检测的U盘目录中如果发生文件变动,其回调通知函数可能都运行在不同的线程,如下:

C#的并发机制有什么优势

Invoke机制的背后其实就是保证所有对于files这个共享变量的操作,全部都是由一个线程执行完成的。

C#的并发机制有什么优势

目前.Net的代码都开源的,下面我们大致讲解一下Invoke的调用过程,不管是BeginInvoke还是Invoke背后其实都是调用的MarshaledInvoke方法来完成的,如下:

public IAsyncResult BeginInvoke(Delegate method, params Object[] args) {            using (new MultithreadSafeCallScope()) {                Control marshaler = FindMarshalingControl();                return(IAsyncResult)marshaler.MarshaledInvoke(this, method, args, false);            }        }

MarshaledInvoke的主要工作是创建ThreadMethodEntry对象,并把它放在一个链表里进行管理,然后调用PostMessage将相关信息发给要通信的线程,如下:

private Object MarshaledInvoke(Control caller, Delegate method, Object[] args, bool synchronous) {            if (!IsHandleCreated) {                throw new InvalidOperationException(SR.GetString(SR.ErrorNoMarshalingThread));            }             ActiveXImpl activeXImpl = (ActiveXImpl)Properties.GetObject(PropActiveXImpl);            if (activeXImpl != null) {                IntSecurity.UnmanagedCode.Demand();            }             // We don't want to wait if we're on the same thread, or else we'll deadlock.             // It is important that syncSameThread always be false for asynchronous calls.             //             bool syncSameThread = false;            int pid; // ignored            if (SafeNativeMethods.GetWindowThreadProcessId(new HandleRef(this, Handle), out pid) == SafeNativeMethods.GetCurrentThreadId()) {                if (synchronous)                    syncSameThread = true;            }             // Store the compressed stack information from the thread that is calling the Invoke()            // so we can assign the same security context to the thread that will actually execute            // the delegate being passed.            //             ExecutionContext executionContext = null;            if (!syncSameThread) {                executionContext = ExecutionContext.Capture();            }             ThreadMethodEntry tme = new ThreadMethodEntry(caller, this, method, args, synchronous, executionContext);            lock (this) {                if (threadCallbackList == null) {                    threadCallbackList = new Queue();                }            }             lock (threadCallbackList) {                if (threadCallbackMessage == 0) {                    threadCallbackMessage = SafeNativeMethods.RegisterWindowMessage(Application.WindowMessagesVersion + "_ThreadCallbackMessage");                }                threadCallbackList.Enqueue(tme);            }             if (syncSameThread) {                InvokeMarshaledCallbacks();            }  else {                //                UnsafeNativeMethods.PostMessage(new HandleRef(this, Handle), threadCallbackMessage, IntPtr.Zero, IntPtr.Zero);            }             if (synchronous) {                if (!tme.IsCompleted) {                    WaitForWaitHandle(tme.AsyncWaitHandle);                }                 if (tme.exception != null) {                    throw tme.exception;                }                return tme.retVal;            }            else {                return(IAsyncResult)tme;            }        }

Invoke的机制就保证了一个共享变量只能由一个线程维护,这和GO语言使用通信来替代共享内存的设计是暗合的,他们的理念都是 "让同一块内存在同一时间内只被一个线程操作" 。这和现代计算体系结构的多核CPU(SMP)有着密不可分的联系,

这里我们先来科普一下CPU之间的通信MESI协议的内容。我们知道现代的CPU都配备了高速缓存,按照多核高速缓存同步的MESI协议约定,每个缓存行都有四个状态,分别是E(exclusive)、M(modified)、S(shared)、I(invalid),其中:

M:代表该缓存行中的内容被修改,并且该缓存行只被缓存在该CPU中。这个状态代表缓存行的数据和内存中的数据不同。

E:代表该缓存行对应内存中的内容只被该CPU缓存,其他CPU没有缓存该缓存对应内存行中的内容。这个状态的缓存行中的数据与内存的数据一致。

I:代表该缓存行中的内容无效。

S:该状态意味着数据不止存在本地CPU缓存中,还存在其它CPU的缓存中。这个状态的数据和内存中的数据也是一致的。不过只要有CPU修改该缓存行都会使该行状态变成 I 。

四种状态的状态转移图如下:

C#的并发机制有什么优势

我们上文也提到了,不同的线程是有大概率是运行在不同CPU核上的,在不同CPU操作同一块内存时,站在CPU0的角度上看,就是CPU1会不断发起remote write的操作,这会使该高速缓存的状态总是会在S和I之间进行状态迁移,而一旦状态变为I将耗费比较多的时间进行状态同步。

C#的并发机制有什么优势

因此我们可以基本得出 this.Invoke(new DeleteDelegate(DeleteFileHandler), new object[] { sender,e });   ;这行看似无关紧要的代码之后,无意中使files共享变量的维护操作,由多核多线程共同操作,变成了众多子线程向主线程通信,所有维护操作均由主线程进行,这也使最终的执行效率有所提高。

深度解读,为何要加两把锁

在当前使用通信替代共享内存的大潮之下,锁其实是最重要的设计。

我们看到在.Net的Invoke实现中,使用了两把锁lock (this) lock (threadCallbackList)

lock (this) {                if (threadCallbackList == null) {                    threadCallbackList = new Queue();                }            }             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。伪代码演示如下:

void CAS(int p, int old,int new){    if *p != old        do nothing    else      *p ← new}

基于CAS的锁效率没问题,尤其是在没有多核竞争的情况CAS表现得尤其优秀,但CAS最大的问题就是不公平,因为如果有多个CPU同时在申请一把锁,那么刚刚释放锁的CPU极可能在下一轮的竞争中获取优势,再次获得这把锁,这样的结果就是一个CPU忙死,而其它CPU却很闲,我们很多时候诟病多核SOC“一核有难,八核围观”其实很多时候都是由这种不公平造成的。

为了解决CAS的不公平问题,业界大神们又引入了TAS(Test And Set Lock)机制,个人感觉还是把TAS中的T理解为Ticket更好记一些,TAS方案中维护了一个请求该锁的头尾索引值,由"head"和"tail"两个索引组成。

struct lockStruct{    int32 head;    int32 tail;} ;

"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#的并发机制有什么优势

lock (threadCallbackList) {                if (threadCallbackMessage == 0) {                    threadCallbackMessage = SafeNativeMethods.RegisterWindowMessage(Application.WindowMessagesVersion + "_ThreadCallbackMessage");                }                threadCallbackList.Enqueue(tme);            }

当队首指向这个tme时,消息才被发送,其实是一种类似于MAS的实现,当然MAS实际是为每个CPU都建立了一个专属的队列,和Invoke的设计略有不同,不过基本的思想是一致的。

感谢你能够认真阅读完这篇文章,希望小编分享的“C#的并发机制有什么优势”这篇文章对大家有帮助,同时也希望大家多多支持编程网,关注编程网行业资讯频道,更多相关知识等着你来学习!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

C#的并发机制有什么优势

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

C#的并发机制有什么优势

这篇文章主要介绍了C#的并发机制有什么优势,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。一行没用的代码却提高了效率?由于我需要记录的文件拷出信息并没有回显在UI的需要,因此也
2023-06-29

C#的并发机制有什么优点

小编给大家分享一下C#的并发机制有什么优点,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!一行没用的代码却提高了效率?由于我需要记录的文件拷出信息并没有回显在UI的
2023-06-29

C++语言有什么优势

这篇文章主要讲解了“C++语言有什么优势”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“C++语言有什么优势”吧!入门简介    C++是个强大的语言,可以用于做别的语言做不了的工作。但是,这
2023-06-17

MongoDB的并发控制与锁机制是什么

MongoDB使用乐观并发控制(Optimistic Concurrency Control)来处理并发操作。在MongoDB中,并发操作通过基于文档级别的锁来实现。当一个客户端请求对一个文档进行更新时,MongoDB会先获取该文档的锁,然
MongoDB的并发控制与锁机制是什么
2024-05-07

Golang中的并发模型有哪些优势

Golang中的并发模型有以下优势:轻量级线程:Golang使用轻量级的goroutine来实现并发,相比于传统的线程模型,goroutine的创建和销毁成本更低,可以轻松创建数以千计的goroutine,而不会导致系统资源的枯竭。通道通信
Golang中的并发模型有哪些优势
2024-03-13

C#并发编程和线程同步机制是什么

C#并发编程是指在C#程序中同时执行多个任务的能力。线程同步机制是确保多个线程能够安全地访问共享资源的方法。在C#中,线程同步可以通过以下方式实现:使用锁机制(lock):通过在代码块中使用lock关键字来锁定共享资源,确保在任意时刻只有一
C#并发编程和线程同步机制是什么
2024-03-07

日本vps主机有什么优势

优质的网络连接:日本拥有先进的网络基础设施,提供高速、稳定的网络连接,适合需要快速访问亚洲市场的网站或应用程序。优质的数据中心:日本的数据中心设施通常拥有先进的设备和安全措施,保障数据的安全和可靠性。更接近亚洲市场:日本的VPS主机位于亚洲
日本vps主机有什么优势
2024-04-19

河南VPS主机有什么优势

稳定性:河南VPS主机提供商通常拥有先进的服务器设备和技术团队,能够保障服务器的稳定运行。价格优势:相比于传统的独立服务器,河南VPS主机的价格通常更为实惠,适合中小型企业和个人用户使用。灵活性:河南VPS主机提供商通常提供不同的套餐选
河南VPS主机有什么优势
2024-04-23

韩国云主机有什么优势

性能稳定:韩国云主机采用先进的硬件设备和高速网络连接,保证了稳定的性能表现,用户可以更加稳定地运行自己的业务。低延迟:韩国云主机在韩国境内架设服务器,可以有效减少数据传输的延迟,提升用户访问网站的速度和体验。安全性高:韩国云主机提供全面的安
韩国云主机有什么优势
2024-04-16

台湾云主机有什么优势

稳定性高:台湾地理位置优越,地震少、自然灾害少,网络稳定性较高,保障了云主机的稳定性和可靠性。带宽速度快:台湾的网络带宽速度相对较快,能够提供给用户更快的访问速度和更好的用户体验。安全性高:台湾云主机提供商在数据保护和信息安全方面有较高
台湾云主机有什么优势
2024-04-16

虚拟云主机有什么优势

虚拟云主机具有以下优势:1. 弹性扩展:虚拟云主机可以根据用户的需求进行弹性扩展,可以随时根据实际需要增加或减少计算资源,避免了传统物理主机资源的浪费或不足的问题。2. 高可用性:虚拟云主机部署在云平台上,利用云平台提供的资源池和容灾机制,
2023-09-13

香港云主机有什么优势

香港云主机有以下几个优势:稳定可靠:香港云主机的数据中心设施较为先进,网络稳定性较高,能够保证用户的业务数据安全和稳定运行。低延迟:香港云主机地理位置靠近中国大陆和东南亚地区,可以提供较低的网络延迟,加快数据传输速度,提升用户体验。适应多样
香港云主机有什么优势
2024-04-15

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录