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

news2024/11/24 6:57:14

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

        因篇幅所限,本章为第3篇。本章主要介绍信号原语。

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


6、信号原语

        并行编程的一个重要方面是任务协调。

        在创建任务时,可能会遇到 生产者/消费者(Producer/Consumer)场景,其中一个线程(消费者)在等待另一个线程(生产者)更新共享资源。由于消费者不知道生产之何时更新共享资源,因此它将轮询共享资源,可能导致竞争,且轮询的效率很低。最好使用 .NET Framework 提供的信号原语(Singaling Primitive):消费者线程将暂停,直到从生产者线程接收到信号为止。

6.1、Thread.Join

Thread.Join 方法 (System.Threading) | Microsoft Learn在此实例表示的线程终止前,阻止调用线程。https://learn.microsoft.com/zh-cn/dotnet/api/system.threading.thread.join?view=netstandard-2.1#system-threading-thread-join        Thread.Join 是使线程等待另一个线程信号的最简单方法,示例代码如下:

        private void RunWithThreadJoin()
        {
            int result = 0;
            Thread th1 = new Thread(() =>
            {
                Debug.Log("Th1 Start !");
                Thread.Sleep(1000);
                result = 100;
                Debug.Log("Th1 End !");
            });

            Thread th2 = new Thread(() =>
            {
                Debug.Log($"Th2 Result Start: {result}");
                th1.Join();
                Debug.Log($"Th2 Result End: {result}");
            });

            th1.Start();
            th2.Start();
        }

        此代码运行结果如下:

6.2、AutoResetEvent

        AutoResetEvent 是指自动重置的 WaitHandle 类。重置后,允许一个线程通过创建的屏障,一旦线程通过,它们就会再次被设置,从而阻塞线程直到下一个信号。

AutoResetEvent 类 (System.Threading) | Microsoft Learn表示线程同步事件在一个等待线程释放后收到信号时自动重置。 此类不能被继承。https://learn.microsoft.com/zh-cn/dotnet/api/system.threading.autoresetevent?view=netstandard-2.1        首先我们写一段没有线程安全的代码:

        private void RunWithAutoResetEvent()
        {
            int result = 0;
            Parallel.For(0, 1001, x =>
            {
                result += x;
            });
            Debug.Log($"Result = {result}");
        }

        显然这段代码是的结果,理论上是 500500,但实际上多运行几次,结果总会有所不同:

         这个原因就不赘述了,很显然了。下面用 AutoResetEvent 来进行改造:

        private void RunWithAutoResetEvent()
        {
            AutoResetEvent autoResetEvent = new AutoResetEvent(false);

            Task.Run(() =>
            {
                int result = 0;
                autoResetEvent.Set();
                Parallel.For(0, 1001, x =>
                {
                    autoResetEvent.WaitOne(100);//会阻塞线程!
                    result += x;
                    autoResetEvent.Set();
                });
                Debug.Log($"Result = {result}");
            });
        }

        最后结果达到预期,没有了线程竞争的问题:

 (17次执行结果都是500500)

6.3、ManualResetEvent

ManualResetEvent 类 (System.Threading) | Microsoft Learn表示线程同步事件,收到信号时,必须手动重置该事件。 此类不能被继承。https://learn.microsoft.com/zh-cn/dotnet/api/system.threading.manualresetevent?source=recommendations&view=netstandard-2.1        ManualResetEvent 是指需要手动重置的等待句柄,允许多个线程通过,直到它再次被设置。


        private ManualResetEvent manualResetEvent = new ManualResetEvent(false);
        private bool IsReset;

        private void RunWithManualResetEvent()
        {
            Task.Run(async () =>
            {
                for (int i = 0; i < 10; i++)
                {
                    manualResetEvent.WaitOne();
                    await Task.Delay(1000);
                    Debug.Log("Task 1  Loop :  " + i);
                }
            });

            Task.Run(async () =>
            {
                for (int i = 0; i < 10; i++)
                {
                    manualResetEvent.WaitOne();
                    await Task.Delay(1000);
                    Debug.Log("Task 2  Loop :  " + i);
                }
            });
        }

        private void SetManualResetEvent()
        {
            if (IsReset)
                manualResetEvent.Reset();
            else
                manualResetEvent.Set();

            IsReset = !IsReset;
        }

        如上述代码,Task 1 和 Task 2 都可以被暂时挂起等待。而我们设置的 manualResetEvent 可以同时管理这两个线程任务的等待。

6.4、WaitHandle

        WaitHandle 是继承 MarshalByRefObject 的抽象类,用于同步应用程序中的线程。调用 WaitHandle 类的任何方法都可以阻塞线程,而释放线程取决于选择的 Signaling 构造的类型。

WaitHandle 类 (System.Threading) | Microsoft Learn封装等待对共享资源进行独占访问的操作系统特定的对象。https://learn.microsoft.com/zh-cn/dotnet/api/system.threading.waithandle?view=netstandard-2.1

        其实在之前章节我们已经用过 WaitHandle 了,AutoResetEvent 和 ManualResetEvent 都是继承自 WaitHandle 。通过之前章节的使用,相信大家已经知道这个类的基本用法了。这里单独说一下 WaitHanlde 的两个静态方法:

  • WaitAll:线程等待数组中的所有等待句柄收到信号。

  • WaitAny:线程等待指定一组等待句柄中的任何一个被发出信号。

        我们写一段示例代码:

        public static void RunWithWaitHandle()
        {
            AutoResetEvent[] waitHandles =
            {
                new AutoResetEvent(false),
                new AutoResetEvent(false),
            };

            RandomTimeToSet(waitHandles[0]);
            RandomTimeToSet(waitHandles[1]);

            WaitHandle.WaitAll(waitHandles);//等待2个任务的信号
            //WaitHandle.WaitAny(waitHandles);//等待2个任务中任意一个信号
            Debug.Log($"RunWithWaitHandle 执行完成 !");
        }
        
        public static void RandomTimeToSet(AutoResetEvent handle)
        {
            Task.Run(async () =>
            {
                System.Random random = new System.Random();
                int waitTime = random.Next(1000, 10000);
                Debug.Log($"开始等待 {waitTime} !");
                await Task.Delay(waitTime);
                Debug.Log($"等待 {waitTime} 完成");
                handle.Set();//发出信号
            });
        }

        这里我们用 WaitAll 来测试,结果如下:

        当然,如果用 WaitAny 则只需要等待其中一个任务发出信号即可。

        这里值得一提的还有一个 API:

  • SignalAndWait:向一个 WaitHandle 发出信号并等待另一个。

        直接看说明比较抽象,这里直接展示代码:

        public static void RunWtihWatiHandleSignalAndWait()
        {
            var autoResetEvent1 = new AutoResetEvent(false);
            var autoResetEvent2 = new AutoResetEvent(false);

            WaitSingalToDebug(autoResetEvent1);
            RandomTimeToSet(autoResetEvent2);

            Task.Run(async () =>
            {
                await Task.Delay(500);
                WaitHandle.SignalAndWait(autoResetEvent1, autoResetEvent2);
                Debug.Log($"RunWtihWatiHandleSignalAndWait 执行完成 !");
            });
        }
        
        public static void WaitSingalToDebug(AutoResetEvent handle) 
        {
            Task.Run(() =>
            {
                Debug.Log("WaitSingalToDebug 开始等待!");
                handle.WaitOne();
                Debug.Log("WaitSingalToDebug 等待完成!");
            });
        }

        执行结果如下:

         可以看到,SignalAndWait 这一行代码直接相当于同时执行2个操作:Set 和 WaitOne 。立即向 autoResetEvent1 发出信号使得 WaitSingalToDebug 这个任务能够正常执行;同时又等待 RandomTimeToSet 任务的信号以继续。

        WaitHandle 的用处还是很有用的。有时我们并不想等待其他任务执行完成,又需要其他任务提供一个节点以继续运行,此时使用 WaitHandle 会是不错的选择。


        (未完待续)

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

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

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

相关文章

浅谈企业能源管理系统在水泥企业的设计与应用

安科瑞 华楠 摘要: 水泥企业是我国高能耗行业之一&#xff0c;一直是政府推进节能减排工作的控制对象。建立企业能源管理系统&#xff0c;实现从能源因素采集、过程监控、能耗管理控制全过程的自动化、科学化管理。加大能源的合理化使用&#xff0c;减少能源消耗&#xff0c;对…

UE初级-C++基础-1.类库简介及目录结构

原文链接&#xff1a;UE5 C教程&#xff08;一、基本结构&#xff09;_skycol的博客-CSDN博客 原文链接&#xff1a;UE4C学习篇&#xff08;一&#xff09;_卡西莫多说的博客-CSDN博客 一.类库简介 虚幻API由三部分组成&#xff0c;分别是Runtime, Developer utilitiew&#…

门票售罄+酒店满房+营收创新高 原拓·非遗嘉年华战报来袭

7月2日&#xff0c;原拓非遗文化嘉年华首站在北京圆满落幕。作为原拓酒店创立的品牌活动IP&#xff0c;原拓非遗文化嘉年华携手非遗传承人、艺术创作者、文创品牌等志同道合的伙伴&#xff0c;在酒店空间呈现丰富体验&#xff0c;通过 DIY 工作坊、创意市集等方式&#xff0c;让…

【机器人模拟-02】 模拟移动机器人设置里程计

一、说明 在本教程中,我将向您展示如何设置移动机器人的测程。本教程是“机器人模拟”指南中的第二个教程。测量位移是仿真中的重要内容,设置测程的官方教程在此页面上,但我将逐步引导您完成整个过程。 您可以在此处获取此项目的完整代码。让我们开始吧! 二、ROS 2 中的里程…

MySQL查询数据库、表以及表行数所占内存大小

前言 在开发中我们想知道某个数据库,或者某个数据表的内存大小,那么怎么查询呢?下面通过简单示例一一介绍,希望对您有所帮助。 某表每行所占字节数 SELECT TABLE_NAME , CONCAT((DATA_LENGTH + INDEX_LENGTH), 字节) AS 每行占用字节数 FROM information_schema.TAB…

高并发的哲学原理(六)-- 拆分网络单点(下):SDN 如何替代百万人民币的负载均衡硬件

上一篇文章的末尾&#xff0c;我们利用负载均衡器打造了一个五万 QPS 的系统&#xff0c;本篇文章我们就来了解一下负载均衡技术的发展历程&#xff0c;并一起用 SDN&#xff08;软件定义网络&#xff09;技术打造出一个能够扛住 200Gbps 的负载均衡集群。 负载均衡发展史 F5 …

备战秋招 | 笔试强训4

目录 一、选择题 二、编程题 三、选择题题解 四、编程题题解 一、选择题 1、有以下程序&#xff0c;程序运行后的输出结果是&#xff08;&#xff09; #include<iostream> #include<cstdio> using namespace std; int main() {int m0123, n123;printf(&quo…

【LeetCode: 931. 下降路径最小和 | 暴力递归=>记忆化搜索=>动态规划 】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

中国8K摄像机厂家加入国际广电设备制造商协会IABM

近日&#xff0c;BOSMA博冠正式成为国际广电设备制造商协会IABM会员&#xff0c;标志着中国8K摄像机厂家BOSMA博冠在广播电视超高清前端采集领域受到全球广电权威机构认可&#xff0c;进一步推动国产品牌在全球市场竞争中提升品牌知名度、加强行业影响力。 IABM创立于1976年&am…

【Unity面试篇】Unity 面试题总结甄选 |C#基础篇 | ❤️持续更新❤️

前言 关于Unity面试题相关的所有知识点&#xff1a;&#x1f431;‍&#x1f3cd;2023年Unity面试题大全&#xff0c;共十万字面试题总结【收藏一篇足够面试&#xff0c;持续更新】为了方便大家可以重点复习某个模块&#xff0c;所以将各方面的知识点进行了拆分并更新整理了新…

android更换开机动画

android11 路径&#xff1a;device / {vendor-name} / {platform-name} / {device-name} / system / bootanimation.zip 例&#xff1a;android \ device \ softwinner \ ceres \ ceres-b6 \ system \ bootanimation.zip android13 路径&#xff1a;device / softwinner / {PRO…

2023年最全最新的学习课程合集

WEB前端入门&#xff1a;从零开始做网站。 完成所有课堂练习就可以做出自己的作品&#xff0c;并掌握数据库和了解开源项目。 这些课程涵盖了HTML、CSS和JavaScript等前端技术&#xff0c;以及与之相关的设计原则和最佳实践。 前端课程的目标是培养学生在网页开发方面的技能…

uniCloud

uniCloud 一、介绍1.新建项目2.初识云函数 二、数据库1.在云控制台操作数据库2.通过云函数对数据库进行操作&#xff08;1&#xff09;查询&#xff08;2&#xff09;修改&#xff08;3&#xff09;删除 3.客户端操作4.JQL语法 三、云函数四、云对象五、云存储六、uni-id七、扩…

2023年7月深圳软考中级系统集成项目管理工程师报名

系统集成项目管理工程师是全国计算机技术与软件专业技术资格&#xff08;水平&#xff09;考试&#xff08;简称软考&#xff09;项目之一&#xff0c;是由国家人力资源和社会保障部、工业和信息化部共同组织的国家级考试&#xff0c;既属于国家职业资格考试&#xff0c;又是职…

EasyCVR接口调用正常,但快照不显示是什么原因?

有用户反馈&#xff0c;现场部署了EasyCVR v3.1版本&#xff0c;将设备通过国标GB28181协议接入到平台&#xff0c;但是页面没有显示出快照&#xff0c;请求我们协助排查。 针对用户的反馈&#xff0c;我们立即排查。排查后发现&#xff0c;视频播放是正常的&#xff0c;调用接…

玩转字符串函数与字符函数——【C语言】

在C语言的学习中&#xff0c;我们经常会遇到字符串&#xff0c;对它的处理也是数不胜数&#xff0c;但是我们没有很好的处理办法&#xff0c;字符串只能放在字符数组或常量字符串中通过自定义函数去使用处理。而现在我将带领大家学习C语言函数库中专门处理字符串的函数。 这些…

超高性能MCU发布,为开发人员提供了高效的工具链

近日嵌入式开发软件和服务的全球领导者IAR与业界领先的半导体器件供应商兆易创新(GigaDevice)宣布&#xff0c;联合推出最新版本IAR Embedded Workbench for Arm 9.40已经全面支持兆易创新的GD32H737/757/759系列超高性能MCU微控制器&#xff0c;这为**ERP系统**开发人员提供了…

Java 注解使用

一、注解简介 注解是那些插入到源代码中使用其他工具可以对其进行处理的标签。这些工具可以在源码层次上进行操作&#xff0c;或者可以处理编译器在其中放置了注解的类文件。 注解不会改变程序的编译方式。Java编译器对于包含注解和不包含注解的代码会生成相同的虚拟机指令。 为…

7种优秀的电商API命名方式

本文通过展示7种优秀API命名实践&#xff0c;来协助您创建高效的API端点&#xff0c;为用户提供更好的使用体验。 如今&#xff0c;API已成为了现代化编程的基本组成部分。它们不但能够改善不同开发团队的协作、并鼓励创新&#xff0c;而且能够提高应用程序的安全性。而作为两…

用PCB加热PCB——PCB加热台

之前为了焊接一些小贴片模块&#xff0c;想过买一个加热台&#xff0c;后来一搜加热台&#xff0c;发现有很多卖PTC加热板的&#xff0c;就又想自己做一个加热台。正好这个月嘉立创又送了打样券&#xff0c;搞起来~ PCB加热台设计主要考虑以下几个方面&#xff1a; 面积功率铜…