前言

这一篇,我们将学习用于实现并行任务、使得多个线程有序同步完成多个阶段的任务。

应用场景主要是控制 N 个线程(可随时增加或减少执行的线程),使得多线程在能够在 M 个阶段中保持同步。

线程工作情况如下:

我们接下来 将学习C# 中的 Barrier ,用于实现并行协同工作。

Barrier 类

使多个任务能够采用并行方式依据某种算法在多个阶段中协同工作,使多个线程(称为“参与者” )分阶段同时处理算法

可以使多个线程(称为“参与者” )分阶段同时处理算法。(注意算法这个词)

每个参与者完成阶段任务后后将被阻止继续执行,直至所有参与者都已达到同一阶段。

Barrier 的构造函数如下:

构造函数 说明
Barrier(Int32) 初始化 Barrier 类的新实例。
Barrier(Int32, Action) 初始化 Barrier 类的新实例。

其中一个构造函数定义如下:

  1. public Barrier (int participantCount, Action<Barrier> postPhaseAction);

participantCount :处于的线程数量,大于0并且小于32767。

postPhaseAction :在每个阶段后执行 Action(委托)。

属性和方法

在还没有清楚这个类有什么作用前,我们来看一下这个类的常用属性和方法。

大概了解 Barrier 有哪些常用属性和方法后,我们开始编写示例代码。

属性:

属性 说明
CurrentPhaseNumber 获取屏障的当前阶段的编号。
ParticipantCount 获取屏障中参与者的总数。
ParticipantsRemaining 获取屏障中尚未在当前阶段发出信号的参与者的数量。

方法:

方法 说明
AddParticipant() 通知 Barrier,告知其将会有另一个参与者。
AddParticipants(Int32) 通知 Barrier,告知其将会有多个其他参与者。
RemoveParticipant() 通知 Barrier,告知其将会减少一个参与者。
RemoveParticipants(Int32) 通知 Barrier,告知其将会减少一些参与者。
SignalAndWait() 发出参与者已达到屏障并等待所有其他参与者也达到屏障。
SignalAndWait(CancellationToken) 发出参与者已达到屏障的信号,并等待所有其他参与者达到屏障,同时观察取消标记。
SignalAndWait(Int32) 发出参与者已达到屏障的信号,并等待所有其他参与者也达到屏障,同时使用 32 位带符号整数测量超时。
SignalAndWait(Int32, CancellationToken) 发出参与者已达到屏障的信号,并等待所有其他参与者也达到屏障,使用 32 位带符号整数测量超时,同时观察取消标记。
SignalAndWait(TimeSpan) 发出参与者已达到屏障的信号,并等待所有其他参与者也达到屏障,同时使用 TimeSpan 对象测量时间间隔。
SignalAndWait(TimeSpan, CancellationToken) 发出参与者已达到屏障的信号,并等待所有其他参与者也达到屏障,使用 TimeSpan 对象测量时间间隔,同时观察取消标记。

Barrier 翻译屏障,前面所说的 “阶段”,在文档中称为屏障,官方有一些例子和实践场景:

https://docs.microsoft.com/zh-cn/dotnet/standard/threading/barrier?view=netcore-3.1

https://docs.microsoft.com/zh-cn/dotnet/standard/threading/how-to-synchronize-concurrent-operations-with-a-barrier?view=netcore-3.1

本文的教程比较简单,你可以先看本教程,再去看看官方示例。

示例

假设有个比赛,一个有三个环节,有三个小组参加比赛。

比赛有三个环节,小组完成一个环节后,可以去等待区休息,等待其他小组也完成比赛后,开始进行下一个环节的比赛。

示例如下:

new Barrier(int,Action) 设置有多少线程参与,Action 委托设置每个阶段完成后执行哪些动作。

.SignalAndWait() 阻止当前线程继续往下执行;直到其他完成也执行到此为止。

  1. class Program
  2. {
  3. // Barrier(Int32, Action)
  4. private static Barrier barrier = new Barrier(3, b =>
  5. Console.WriteLine($"\n第 {b.CurrentPhaseNumber + 1} 环节的比赛结束,请评分!"));
  6. static void Main(string[] args)
  7. {
  8. // Random 模拟每个小组完成一个环节比赛需要的时间
  9. Thread thread1 = new Thread(() => DoWork("第一小组", new Random().Next(2, 10)));
  10. Thread thread2 = new Thread(() => DoWork("第二小组", new Random().Next(2, 10)));
  11. Thread thread3 = new Thread(() => DoWork("第三小组", new Random().Next(2, 10)));
  12. // 三个小组开始比赛
  13. thread1.Start();
  14. thread2.Start();
  15. thread3.Start();
  16. Console.ReadKey();
  17. }
  18. static void DoWork(string name, int seconds)
  19. {
  20. // 第一环节
  21. Console.WriteLine($"\n{name}:开始进入第一环节比赛");
  22. Thread.Sleep(TimeSpan.FromSeconds(seconds)); // 模拟小组完成环节比赛需要的时间
  23. Console.WriteLine($"\n {name}:完成第一环节比赛,等待其它小组");
  24. // 小组完成阶段任务,去休息等待其它小组也完成比赛
  25. barrier.SignalAndWait();
  26. // 第二环节
  27. Console.WriteLine($"\n {name}:开始进入第二环节比赛");
  28. Thread.Sleep(TimeSpan.FromSeconds(seconds));
  29. Console.WriteLine($"\n {name}:完成第二环节比赛,等待其它小组\n");
  30. barrier.SignalAndWait();
  31. // 第三环节
  32. Console.WriteLine($"\n {name}:开始进入第三环节比赛");
  33. Thread.Sleep(TimeSpan.FromSeconds(seconds));
  34. Console.WriteLine($"\n {name}:完成第三环节比赛,等待其它小组\n");
  35. barrier.SignalAndWait();
  36. }
  37. }

上面的示例中,每个线程都使用了 DoWork() 这个方法去中相同的事情,当然也可以设置多个线程执行不同的任务,但是必须保证每个线程都具有相同数量的 .SignalAndWait(); 方法。

当然 SignalAndWait() 可以设置等待时间,如果其他线程迟迟没有到这一步,那就继续运行。可以避免死锁等问题。

到目前,只使用了 SignalAndWait() ,我们继续学习一下 Barrier 类的其他方法。

新的示例

Barrier.AddParticipant():添加参与者;

Barrier.RemoveParticipant():移除参与者;

这里继续使用第二节的示例。

因为这是比赛,老是等待其他小组,会使得比赛进行比较慢。

新的规则:不必等待最后一名,当环节只剩下最后一名时为完成时,其它小组可以立即进行下一个环节的比赛。

​ 当然,最后一名小组,有权利继续完成比赛。

修改第二小节的代码,在 Main 内第一行加上 barrier.RemoveParticipant();

  1. static void Main(string[] args)
  2. {
  3. barrier.RemoveParticipant();
  4. ... ...

试着再运行一下。

说明

SignalAndWait() 的 重载比较多,例如 SignalAndWait(CancellationToken),这里笔者先不讲解此方法如何使用。等到写到后面的异步(Task),读者学到相关的知识点,我们再过一次复习,这样由易到难,自然水到渠成。

Barrier 适合用于同时执行相同流程的工作,因为工作内容是相同的,便于协同。工作流有可能用得上吧。

但是 Barrier 更加适合用于算法领域,可以参考:https://devblogs.microsoft.com/pfxteam/parallel-merge-sort-using-barrier/

当然,后面学习异步和并行编程后,也会编写相应的算法示例。

C#多线程(9):多阶段并行线程的更多相关文章

  1. C#线程 并行线程

    第五部分 并行线程   在本节中,我们将介绍Framework 4.0新增的利用多核处理器的多线程API: 并行LINQ或PLINQ Parallel 类 任务并行性构造 并发集合 自旋锁和自旋等待 ...

  2. 5天玩转C#并行和多线程编程 —— 第二天 并行集合和PLinq

    5天玩转C#并行和多线程编程系列文章目录 5天玩转C#并行和多线程编程 —— 第一天 认识Parallel 5天玩转C#并行和多线程编程 —— 第二天 并行集合和PLinq 5天玩转C#并行和多线程编 ...

  3. Java多线程之~~~使用Exchanger在线程之间交换数据[这个结合多线程并行会有解决很多问题]生产者消费者模型

    http://blog.csdn.net/a352193394/article/details/39503857  Java多线程之~~~使用Exchanger在线程之间交换数据[这个结合多线程并行会 ...

  4. SpringBoot开发案例之多任务并行+线程池处理

    前言 前几篇文章着重介绍了后端服务数据库和多线程并行处理优化,并示例了改造前后的伪代码逻辑.当然了,优化是无止境的,前人栽树后人乘凉.作为我们开发者来说,既然站在了巨人的肩膀上,就要写出更加优化的程序 ...

  5. Java线程和多线程(十五)——线程的活性

    当开发者在应用中使用了并发来提升性能的同时,开发者也需要注意线程之间有可能会相互阻塞.当整个应用执行的速度比预期要慢的时候,也就是应用没有按照预期的执行时间执行完毕.在本章中,我们来需要仔细分析可能会 ...

  6. 数据结构(逻辑结构,物理结构,特点) C#多线程编程的同步也线程安全 C#多线程编程笔记 String 与 StringBuilder (StringBuffer) 数据结构与算法-初体验(极客专栏)

    数据结构(逻辑结构,物理结构,特点) 一.数据的逻辑结构:指反映数据元素之间的逻辑关系的数据结构,其中的逻辑关系是指数据元素之间的前后件关系,而与他们在计算机中的存储位置无关.逻辑结构包括: 集合 数 ...

  7. 【CUDA 基础】2.3 组织并行线程

    title: [CUDA 基础]2.3 组织并行线程 categories: CUDA Freshman tags: Thread Block Grid toc: true date: 2018-03 ...

  8. C#多线程(四)并行编程篇之结构化

    前言 在前三章中我们的案例大量使用到了Thread这个类,通过其原始API,对其进行创建.启动.中断.中断.终止.取消以及异常处理,这样的写法不仅不够优雅(对接下来这篇,我称其为.NET现代化并行编程 ...

  9. Java多线程开发系列之三:线程这一辈子(线程的生命周期)

    前文中已经提到了,关于多线程的基础知识和多线程的创建.但是如果想要很好的管理多线程,一定要对线程的生命周期有一个整体概念.本节即对线程的一生进行介绍,让大家对线程的各个时段的状态有一定了解. 线程的一 ...

  10. Java多线程1:进程与线程概述

    进程和线程 谈到多线程,就得先讲进程和线程的概念. 进程 进程可以理解为受操作系统管理的基本运行单元.360浏览器是一个进程.WPS也是一个进程,正在操作系统中运行的".exe"都 ...

随机推荐

  1. pytest-assume插件-多重校验

    自动化接口测试我们通常会对一条case设置多条断言,这样就会出现一个问题,如果前面一 个校验不通过,那么后面的校验就不会走到,如下图,可以看到校验走到assert False就不往 下走了,这个时候p ...

  2. 如何处理开发环境没有问题,线上环境有问题这个bug

    解决思路 首先确认开发环境有没有这个问题: 如果没有这个问题: 将你的地址切换为线上的环境,看看线上环境有没有这个问题: 如果切换为线上环境有这个问题,就可以调试了: 如果切换为线上环境没有这个问题, ...

  3. 【编写环境二】python库scipy.stats各种分布函数生成、以及随机数生成【泊松分布、正态分布等】

    平时我们在编写代码是会经常用到一些随机数,而这些随机数服从一定的概率分布. 1.泊松分布.正态分布等生成方法 1.1常见分布: stats连续型随机变量的公共方法: *离散分布的简单方法大多数与连续分 ...

  4. 2.1 C++ STL 数组向量容器

    Vector容器是C++ STL中的一个动态数组容器,可以在运行时动态地增加或减少其大小,存储相同数据类型的元素,提供了快速的随机访问和在末尾插入或删除元素的功能. 该容器可以方便.灵活地代替数组,容 ...

  5. C/C++ Npcap包实现数据嗅探

    npcap 是Nmap自带的一个数据包处理工具,Nmap底层就是使用这个包进行收发包的,该库,是可以进行二次开发的,不过使用C语言开发费劲,在进行渗透任务时,还是使用Python构建数据包高效,这东西 ...

  6. [XXL-JOB] 项目集成-Framework

    1.导入pom坐标 <dependency> <groupId>com.hbasesoft.framework</groupId> <artifactId&g ...

  7. 给textarea添加行号,textarea使用代码风格的一些思考

    背景 项目有个需求是 在textarea中编辑脚本并显示为代码风格样式,显示行号: textarea显示行号 思路: 1.监听textarea内容变化,执行一个change函数,解析内容里面有多少个换 ...

  8. 京豆薅羊毛新姿势-docker方式

    背景 上周看脉脉的时候看到下面这个帖子,领导让搞项目容器化,但是楼主没搞过,对新东西有畏惧感,怂了,然后把机会白白送给其他同事了. 想来我也是差不多这样的,刚到阿里工作的时候,有个好的项目机会来了,领 ...

  9. c语言实现内存池

    概要 所谓内存池,顾名思义和线程池的设计原理是一样的,为了减少频繁申请释放内存而带来的资源消耗,减少释放内存后产生的内存碎片. 设计理念 为了方便管理内存池的设计通常是划分出一定数量的内存块,这些内存 ...

  10. 《ASP.NET Core 与 RESTful API 开发实战》-- (第8章)-- 读书笔记(下)

    第 8 章 认证和安全 8.3 HTTPS HTTP 协议能够在客户端和服务器之间传递信息,特点是以明文的方式发送内容,并不提供任何方式的数据加密 为了解决 HTTP 协议这一缺陷,需要使用另一种协议 ...