【C#】并行编程实战:同步原语(4)

news2025/1/18 11:54:51

        在第4章中讨论了并行编程的潜在问题,其中之一就是同步开销。当将工作分解为多个工作项并由任务处理时,就需要同步每个线程的结果。线程局部存储和分区局部存储,某种程度上可以解决同步问题。但是,当数据共享时,就需要用到同步原语。

        因篇幅所限,本章为第4篇,主要介绍轻量级同步原语、屏障和倒数事件、SpinWait和自旋锁。

        本教程对应学习工程:魔术师Dix / HandsOnParallelProgramming · GitCode


7、轻量级同步原语

        .NET Framework 还提供了轻量级的同步原语,其性能比同步原语更好。它们尽可能避免依赖内核对象(如等待句柄),因此它们只在进程内部工作。适合在线程等待时间很短的时候使用。

        从微软的介绍来看,在单进程工作的情况下,轻量级同步原语性能会更好,使用上也是相同的。建议作为优先选择。

7.1、ReaderWriterLockSlim

        相当于 ReaderWriterLock 的轻量级实现。其允许多个线程访问受保护资源,而仅允许一个线程写入。

ReaderWriterLockSlim 类 (System.Threading) | Microsoft Learn表示用于管理资源访问的锁定状态,可实现多线程读取或进行独占式写入访问。 icon-default.png?t=N6B9https://learn.microsoft.com/zh-cn/dotnet/api/system.threading.readerwriterlockslim?view=netstandard-2.1

        虽然 ReaderWriterLockSlim 类似于 ReaderWriterLock,但不同之处在于,前者简化了递归规则以及锁状态的升级和降级规则。 ReaderWriterLockSlim 避免了许多潜在的死锁情况。

        另外,ReaderWriterLockSlim 的性能显著优于 ReaderWriterLock。 建议对所有新开发的项目使用 ReaderWriterLockSlim。

7.2、SemaphoreSlim

        轻量级信号灯 SemaphoreSlim 是 Semaphore 的轻量级实现,限制了多线程的访问。SemaphoreSlim 只能是局部信号灯,而 Semaphore 可以创建为全局信号灯

SemaphoreSlim 类 (System.Threading) | Microsoft Learn对可同时访问资源或资源池的线程数加以限制的 Semaphore 的轻量替代。 icon-default.png?t=N6B9https://learn.microsoft.com/zh-cn/dotnet/api/system.threading.semaphoreslim?view=netstandard-2.1

        使用区别就是用 Wait 方法替代了 WaitOne 方法。

7.3、ManualResetEventSlim

        ManualResetEventSlim 是 ManualResetEvent 的轻量级实现,具有更好的性能。使用时也是,用 Wait 方法替代了 WaitOne 方法。

ManualResetEventSlim 类 (System.Threading) | Microsoft Learn表示线程同步事件,收到信号时,必须手动重置该事件。 此类是 ManualResetEvent 的轻量替代项。 icon-default.png?t=N6B9https://learn.microsoft.com/zh-cn/dotnet/api/system.threading.manualreseteventslim?view=netstandard-2.1        从介绍来看,在单进程中,使用 ManualResetEventSlim 大部分情况下都有更好的优势。

8、屏障和倒数事件

        .NET Framework 有一些内置的信号原语,可以帮助我们同步多个线程。

8.1、CountDownEvent

        在计数为 0 时发出信号的倒数事件:

CountdownEvent 类 (System.Threading) | Microsoft Learn表示在计数变为零时处于有信号状态的同步基元。 icon-default.png?t=N6B9https://learn.microsoft.com/zh-cn/dotnet/api/system.threading.countdownevent?view=netstandard-2.1        简单示例代码如下:

        public static void RunWithCountDownEvent()
        {
            CountdownEvent countdownEvent = new CountdownEvent(5);//等待5个任务完成

            for (int i = 0; i < 10; i++)//同时执行10个任务
                RandomTimtToCountDown(countdownEvent, i);

            Task.Run(() =>
            {
                countdownEvent.Wait();
                Debug.LogError("倒数完成 !");
            });
        }
        
        public static void RandomTimtToCountDown(CountdownEvent countdownEvent, int index)
        {
            Task.Run(async () =>
            {
                System.Random random = new System.Random();
                int waitTime = random.Next(500, 2000);
                //Debug.Log($"开始等待 {waitTime} ms !");
                await Task.Delay(waitTime);
                Debug.Log($"【{index}】等待 {waitTime} ms 完成");
                countdownEvent.Signal();//发出信号
            });
        }

        运行结果如下:

 

        可见,倒数事件能保证5个事件完成时解除阻塞。当我们需要等待多个任务完成一定进度,而不需要在意究竟是哪些任务完成时,就可以使用这个事件。这个我感觉在某些特殊情况很有用,例如我需要同时加载 100 个资源,但是只要加载了其中任意 30 个就可以继续游戏了,就可以使用这个倒数事件。

8.2、Barrier

        允许多个线程运行,而无需主线程控制他们,创建了一个屏障直至所有线程到达。

Barrier 类 (System.Threading) | Microsoft Learn使多个任务能够采用并行方式依据某种算法在多个阶段中协同工作。 icon-default.png?t=N6B9https://learn.microsoft.com/zh-cn/dotnet/api/system.threading.barrier?view=netstandard-2.1        测试代码如下:

        public static void RunWithBarrier()
        {
            Barrier barrier = new Barrier(0);//设定初始参与者为 0 

            RandomTimeWaitBarrier(barrier);
            RandomTimeWaitBarrier(barrier);
            RandomTimeWaitBarrier(barrier);
        }        
        
        public static void RandomTimeWaitBarrier(Barrier barrier)
        {
            barrier.AddParticipant();//添加一个参与者
            Task.Run(async () =>
            {
                System.Random random = new System.Random();
                int waitTime = random.Next(500, 2000);
                await Task.Delay(waitTime);
                Debug.Log($"等待 {waitTime} ms 完成");
                barrier.SignalAndWait();//标记完成,并等待其他任务完成
                Debug.LogError("执行完毕!");
            });
        }

        运行结果如下:

         屏障的作用很明显,就是根据参与者的完成情况,然后同时进行剩余步骤。这个的作用就很多了,例如经常遇到的需求,就是一边加载资源一边等待网络消息,但是要两者都完成才能进行下一步。因为我们不知道哪个任务先完成,一般做法是设置很多 Flag ,互相监听等待情况。使用屏障,就能很方便地能实现这个功能。

9、SpinWait

        在 4.2、阻塞与自旋 中提到过,如果阻塞的时间很短,采用自旋技术比阻塞有效得多。因为自旋减少了上下文切换和转换的开销(也就是上下文切换的开销大于阻塞的开销,使用自旋)。

        SpinWait 的用法也很简单:

SpinWait spin = new SpinWait();
spin.SpinOnce();

        这样就能完成一次极快的阻塞。

        也可以使用 SpinWait.SpinUntil ,传入一个方法,当返回会 true 的时候结束阻塞。

SpinWait 结构 (System.Threading) | Microsoft Learn为基于自旋的等待提供支持。 icon-default.png?t=N6B9https://learn.microsoft.com/zh-cn/dotnet/api/system.threading.spinwait?view=netstandard-2.1        SpinWait 成员不是线程安全的。 如果多个线程必须旋转,则每个线程都应使用其自己的实例 SpinWait。

10、自旋锁

        如果等待时间极短,则锁和互锁原语可能会大大降低性能。自旋锁 (SpinLock)提供了一种轻量级的低级替代选项。SpinLock 即使还没有获得锁,也会产生线程的时间片。

        这里我们又搬出 3.1、重新排序 那一段示例代码,这次我们加上自旋锁:

        public static void RunTestAddFunctionWithSpinLock()
        {
            TestValueA = 0;
            TestValueB = 0;
            m_IsFinishOnce = false;

            Task.Run(() =>
            {
                SpinLock spinLock = new SpinLock();

                Parallel.For(0, 10000, x =>
                {
                    bool lockToken = false;
                    spinLock.Enter(ref lockToken);
                    TestValueA = x;
                    TestValueB = x;
                    m_IsFinishOnce = TestValueA >= TestValueB;
                    spinLock.Exit(false);
                });
                Debug.Log("运行完成");
            });
        }

        这次也达到了其他上锁代码同样的效果,没有再出现取值错误的问题。这里恰好满足自旋锁的使用条件:任务量很小,用自旋锁来避免上下文的切换

SpinLock | Microsoft Learn详细了解:SpinLockicon-default.png?t=N6B9https://learn.microsoft.com/zh-cn/dotnet/standard/threading/spinlock


本章小节:

        本章详细讲解了 .NET Core 提供的同步原语,如果并行代码要保证正确,使用同步原语非常重要。当然,这会带来额外的性能开销,所以最好只在关键节使用。

        另外,尽量使用轻量级同步原语;而且屏障、倒数和自旋也是非常有用的。

         本教程对应学习工程:魔术师Dix / HandsOnParallelProgramming · GitCode

相关链接:

【C#】并行编程实战:同步原语(1)_魔术师Dix的博客-CSDN博客

【C#】并行编程实战:同步原语(2)_魔术师Dix的博客-CSDN博客

【C#】并行编程实战:同步原语(3)_魔术师Dix的博客-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/755159.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用 EMQX 和 eKuiper 进行 MQTT 流处理:快速教程

引言 MQTT 协议是一种专为物联网应用而设计的轻量级消息传输协议。它具有简单、开放、易于实现的特点&#xff0c;是物联网应用的理想选择。MQTT 数据以连续实时的方式进行传输&#xff0c;非常适合由流处理引擎进行处理。 EMQX 是一款大规模分布式物联网 MQTT Broker&#x…

队列--C语言实现数据结构

本期带大家一起用C语言实现队列&#x1f308;&#x1f308;&#x1f308; 文章目录 1、队列的概念2、队列的操作流程3 、队列的结构4、队列的实现4.1 队列的结构设计4.2 队列的初始化4.3 入队4.4 判断队列是否为空4.5 出队4.6 获取队头数据4.7 获取队尾数据4.8 获取队列当中数据…

HTTP1和HTTP2和HTTP3的区别

超文本传输协议是一个简单的请求-响应协议&#xff0c;它通常运行在TCP之上。 目录 HTTP1.1&#xff1a; HTTP2 HTTP3 参考文献 HTTP1.1&#xff1a; 特点&#xff1a; &#xff11;.一条链接只能一次请求一次返回这样子来回。一般的我们浏览器会帮我们一次次请求和收到。…

安卓UI:SearchView

目录 一、SearchView介绍 二、常用方法 &#xff08;一&#xff09;、监听器&#xff1a; (二)、常用方法&#xff1a; (三)、其他常用方法 三、例子&#xff1a; MainActivity2 &#xff1a; ChatListAdapter &#xff1a; item_people_view: activity_main2: 运行结果…

043、TiDB特性_缓存表和分区表

针对于优化器在索引存在时依然使⽤全表扫描的情况下&#xff0c;使⽤缓存表和分区表是提升查询性能的有效⼿段。 缓存表 缓存表是将表的内容完全缓存到 TiDB Server 的内存中表的数据量不⼤,⼏乎不更改读取很频繁缓存控制: ALTER TABLE table_name CACHE|NOCACHE; # 使用tr…

【Ubuntu】安装docker-compose

要在Ubuntu上安装Docker Compose&#xff0c;可以按照以下步骤进行操作&#xff1a; 下载 Docker Compose 二进制文件&#xff1a; sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/loc…

产业大模型刚开卷,京东跑进“最后半公里”

点击关注 文&#xff5c;姚 悦 编&#xff5c;王一粟 “京东一直在探索哪些产品、技术、场景可以真正把大模型用起来&#xff0c;在我们内部的场景中反复验证后&#xff0c;才决定在7月份对外发布&#xff0c;现在我们在零售、健康、物流、金融等业务场景里已经积累了一些经…

架构训练营:3-3设计备选方案与架构细化

3架构中期 什么是备选架构&#xff1f; 备选架构定义了系统可行的架构模式和技术选型 备选方案筛选过程 头脑风暴 &#xff1a;对可选技术进行排列组合&#xff0c;得到可能的方案 红线筛选&#xff1a;根据系统明确的约束和限定&#xff0c;一票否决某些方案&#xff08;主要…

Java分布式项目常用技术栈简介

Spring-Cloud-Gateway : 微服务之前架设的网关服务&#xff0c;实现服务注册中的API请求路由&#xff0c;以及控制流速控制和熔断处理都是常用的架构手段&#xff0c;而这些功能Gateway天然支持 运用Spring Boot快速开发框架&#xff0c;构建项目工程&#xff1b;并结合Spring…

java错误:不支持发行版本5或java: 不再支持源选项 5。请使用 6 或更高版本的解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

Docker本地镜像发布到私有库

Docker Registry&#xff08;Docker镜像仓库&#xff09; 使用Docker Registry&#xff0c;可以创建私有或公共的镜像仓库&#xff0c;以存储Docker镜像。私有仓库可以用于存储公司内部的镜像&#xff0c;或者用于个人项目的镜像。公共仓库则会将发布的镜像分享到全世界。 1 …

【C++】string模拟实现

个人主页&#x1f356;&#xff1a;在肯德基吃麻辣烫 文章目录 前言一、string的成员变量二、string默认成员函数1.构造函数1.1 无参构造(默认构造)1.2 普通构造1.3无参构造和全缺省构造可以合并 浅拷贝和深拷贝2.拷贝构造3.赋值运算符重载4.析构函数 三、[]的下标访问和iterat…

P5 第二章 电阻电路的等效变换

1、电阻的Y形联结和△形联结的等效变换 可以发现电阻的Y形联结和△形联结可以刻画成下图模型&#xff1a; 如果Y形联结和△形联结i1,i2,i3都相等&#xff0c;则可以列公式解出R1,R2,R3之间的大小关系。 电路普遍存在对偶关系&#xff0c;可以将上图的电阻换成电导&#xff0c…

干货 | 一个漏洞利用工具仓库

0x00 Awesome-Exploit 一个漏洞证明/漏洞利用工具仓库 不定期更新 部分漏洞对应POC/EXP详情可参见以下仓库&#xff1a; https://github.com/Threekiii/Awesome-POC https://github.com/Threekiii/Vulhub-Reproduce 0x01 项目导航 ActiveMQ CVE-2015-5254 Apisix CVE-2…

el-upload实现上传文件夹(批量上传文件)

el-upload实现上传文件夹(批量上传文件)&#xff1a;关键代码在于 this.$refs.uploadFolder.$children[0].$refs.input.webkitdirectory true;//让el-upload支持上传文件夹 <template><div class"sg-body"><el-upload ref"uploadFolder" :…

【智能时代的颠覆】AI让物联网不再是物联网

自我介绍⛵ &#x1f4e3;我是秋说&#xff0c;研究人工智能、大数据等前沿技术&#xff0c;传递Java、Python等语言知识。 &#x1f649;主页链接&#xff1a;秋说的博客 &#x1f4c6; 学习专栏推荐&#xff1a;MySQL进阶之路、C刷题集、网络安全攻防姿势总结 欢迎点赞 &…

HTML5 WebSocket介绍与基本使用(解析服务端返回的二进制数据)

WebSocket基本介绍 WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。 WebSocket 使得客户端和服务器之间的数据交换变得更加简单&#xff0c;允许服务端主动向客户端推送数据。在 WebSocket API 中&#xff0c;浏览器和服务器只需要完成一次握手&a…

数据库系统 - 家庭教育平台设计开发

目录 1.绪论 1.1项目背景 1.2家庭教育平台的发展现状与优势 1.2.1国内外发展现状 1.2.2家庭教育平台的优势 2.需求分析 2.1可行性分析 2.1.1经济可行性 2.1.2 技术可行性 2.1.3操作可行性 2.2系统功能 2.2.1 家庭教育资源 2.2.2 家庭教育指导师 2.2.3家庭教育咨询…

H3C-Cloud Lab实验-静态路由配置实验

实验拓扑图&#xff1a; 接口IP地址规划&#xff1a; 实验需求&#xff1a; 1、理解静态路由的运行原理 2、掌握静态路由的配置 实验步骤&#xff1a; PC1和PC2的IP地址、子网掩码、网关 1、连接所有设备并重命名 2、R1&#xff0c;配置R1的接口IP地址&#xff0c;配置3.0、…

042、TiDB特性_系统表的使用

系统表存储位置 MySQL 存储TiDB 系统表 mysql.user 等 information_schmea提供了一种查看系统元数据的方法 与mysql兼容的表&#xff1a;tables、processlist、columns等自定义的表&#xff1a; cluster_config、cluster_hardware、tiflash_replica等等 metrics_schema: 基于P…