前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >4.0中的并行计算和多线程详解(一)

4.0中的并行计算和多线程详解(一)

作者头像
vv彭
发布于 2021-01-27 02:05:51
发布于 2021-01-27 02:05:51
1.7K0
举报
文章被收录于专栏:c#学习笔记c#学习笔记

4.0中的并行计算和多线程详解(一)

转自:https://cloud.tencent.com/developer/article/1780190

并行计算部分

代码语言:txt
AI代码解释
复制
    沿用微软的写法,System.Threading.Tasks.::.Parallel类,提供对并行循环和区域的支持。 我们会用到的方法有For,ForEach,Invoke。

一、简单使用

代码语言:txt
AI代码解释
复制
    首先我们初始化一个List用于循环,这里我们循环10次。(后面的代码都会按这个标准进行循环)

Code

  1. Program.Data = new List<int>();
  2. for (int i = 0; i < 10; i++)
  3. {
  4. Data.Add(i);
  5. }
代码语言:txt
AI代码解释
复制
    下面我们定义4个方法,分别为for,foreach,并行For,并行ForEach。并测试他们的运行时长。

Code

  1. /// <summary>
  2. /// 是否显示执行过程
  3. /// </summary>
  4. public bool ShowProcessExecution = false;
  5. /// <summary>
  6. /// 这是普通循环for
  7. /// </summary>
  8. private void Demo1()
  9. {
  10. List<int> data = Program.Data;
  11. DateTime dt1 = DateTime.Now;
  12. for (int i = 0; i < data.Count; i++)
  13. {
  14. Thread.Sleep(500);
  15. if (ShowProcessExecution)
  16. Console.WriteLine(data[i]);
  17. }
  18. DateTime dt2 = DateTime.Now;
  19. Console.WriteLine("普通循环For运行时长:{0}毫秒。", (dt2 - dt1).TotalMilliseconds);
  20. }
  21. /// <summary>
  22. /// 这是普通循环foreach
  23. /// </summary>
  24. private void Demo2()
  25. {
  26. List<int> data = Program.Data;
  27. DateTime dt1 = DateTime.Now;
  28. foreach (var i in data)
  29. {
  30. Thread.Sleep(500);
  31. if (ShowProcessExecution)
  32. Console.WriteLine(i);
  33. }
  34. DateTime dt2 = DateTime.Now;
  35. Console.WriteLine("普通循环For运行时长:{0}毫秒。", (dt2 - dt1).TotalMilliseconds);
  36. }
  37. /// <summary>
  38. /// 这是并行计算For
  39. /// </summary>
  40. private void Demo3()
  41. {
  42. List<int> data = Program.Data;
  43. DateTime dt1 = DateTime.Now;
  44. Parallel.For(0, data.Count, (i) =>
  45. {
  46. Thread.Sleep(500);
  47. if (ShowProcessExecution)
  48. Console.WriteLine(data[i]);
  49. });
  50. DateTime dt2 = DateTime.Now;
  51. Console.WriteLine("并行运算For运行时长:{0}毫秒。", (dt2 - dt1).TotalMilliseconds);
  52. }
  53. /// <summary>
  54. /// 这是并行计算ForEach
  55. /// </summary>
  56. private void Demo4()
  57. {
  58. List<int> data = Program.Data;
  59. DateTime dt1 = DateTime.Now;
  60. Parallel.ForEach(data, (i) =>
  61. {
  62. Thread.Sleep(500);
  63. if (ShowProcessExecution)
  64. Console.WriteLine(i);
  65. });
  66. DateTime dt2 = DateTime.Now;
  67. Console.WriteLine("并行运算ForEach运行时长:{0}毫秒。", (dt2 - dt1).TotalMilliseconds);
  68. }

下面是运行结果:

这里我们可以看出并行循环在执行效率上的优势了。

结论1:在对一个数组内的每一个项做单独处理时,完全可以选择并行循环的方式来提升执行效率。

原理1:并行计算的线程开启是缓步开启的,线程数量1,2,4,8缓步提升。(不详,PLinq最多64个线程,可能这也是64)

二、 并行循环的中断和跳出

代码语言:txt
AI代码解释
复制
    当在进行循环时,偶尔会需要中断循环或跳出循环。下面是两种跳出循环的方法Stop和Break,LoopState是循环状态的参数。

Code

  1. /// <summary>
  2. /// 中断Stop
  3. /// </summary>
  4. private void Demo5()
  5. {
  6. List<int> data = Program.Data;
  7. Parallel.For(0, data.Count, (i, LoopState) =>
  8. {
  9. if (data[i] > 5)
  10. LoopState.Stop();
  11. Thread.Sleep(500);
  12. Console.WriteLine(data[i]);
  13. });
  14. Console.WriteLine("Stop执行结束。");
  15. }
  16. /// <summary>
  17. /// 中断Break
  18. /// </summary>
  19. private void Demo6()
  20. {
  21. List<int> data = Program.Data;
  22. Parallel.ForEach(data, (i, LoopState) =>
  23. {
  24. if (i > 5)
  25. LoopState.Break();
  26. Thread.Sleep(500);
  27. Console.WriteLine(i);
  28. });
  29. Console.WriteLine("Break执行结束。");
  30. }
代码语言:txt
AI代码解释
复制
    执行结果如下:

结论2:使用Stop会立即停止循环,使用Break会执行完毕所有符合条件的项。

三、并行循环中为数组/集合添加项

代码语言:txt
AI代码解释
复制
    上面的应用场景其实并不是非常多见,毕竟只是为了遍历一个数组内的资源,我们更多的时候是为了遍历资源,找到我们所需要的。那么请继续看。

下面是我们一般会想到的写法:

Code

  1. private void Demo7()
  2. {
  3. List<int> data = new List<int>();
  4. Parallel.For(0, Program.Data.Count, (i) =>
  5. {
  6. if (Program.Data[i] % 2 == 0)
  7. data.Add(Program.Data[i]);
  8. });
  9. Console.WriteLine("执行完成For.");
  10. }
  11. private void Demo8()
  12. {
  13. List<int> data = new List<int>();
  14. Parallel.ForEach(Program.Data, (i) =>
  15. {
  16. if (Program.Data[i] % 2 == 0)
  17. data.Add(Program.Data[i]);
  18. });
  19. Console.WriteLine("执行完成ForEach.");
  20. }

看起来应该是没有问题的,但是我们多次运行后会发现,偶尔会出现错误如下:

这是因为List是非线程安全的类,我们需要使用System.Collections.Concurrent命名空间下的类型来用于并行循环体内。

说明

BlockingCollection<T>

为实现 IProducerConsumerCollection<T> 的线程安全集合提供阻止和限制功能。

ConcurrentBag<T>

表示对象的线程安全的无序集合。

ConcurrentDictionary<TKey, TValue>

表示可由多个线程同时访问的键值对的线程安全集合。

ConcurrentQueue<T>

表示线程安全的先进先出 (FIFO) 集合。

ConcurrentStack<T>

表示线程安全的后进先出 (LIFO) 集合。

OrderablePartitioner<TSource>

表示将一个可排序数据源拆分成多个分区的特定方式。

Partitioner

提供针对数组、列表和可枚举项的常见分区策略。

Partitioner<TSource>

表示将一个数据源拆分成多个分区的特定方式。

那么我们上面的代码可以修改为,加了了ConcurrentQueue和ConcurrentStack的最基本的操作。

Code

  1. /// <summary>
  2. /// 并行循环操作集合类,集合内只取5个对象
  3. /// </summary>
  4. private void Demo7()
  5. {
  6. ConcurrentQueue<int> data = new ConcurrentQueue<int>();
  7. Parallel.For(0, Program.Data.Count, (i) =>
  8. {
  9. if (Program.Data[i] % 2 == 0)
  10. data.Enqueue(Program.Data[i]);//将对象加入到队列末尾
  11. });
  12. int R;
  13. while (data.TryDequeue(out R))//返回队列中开始处的对象
  14. {
  15. Console.WriteLine(R);
  16. }
  17. Console.WriteLine("执行完成For.");
  18. }
  19. /// <summary>
  20. /// 并行循环操作集合类
  21. /// </summary>
  22. private void Demo8()
  23. {
  24. ConcurrentStack<int> data = new ConcurrentStack<int>();
  25. Parallel.ForEach(Program.Data, (i) =>
  26. {
  27. if (Program.Data[i] % 2 == 0)
  28. data.Push(Program.Data[i]);//将对象压入栈中
  29. });
  30. int R;
  31. while (data.TryPop(out R))//弹出栈顶对象
  32. {
  33. Console.WriteLine(R);
  34. }
  35. Console.WriteLine("执行完成ForEach.");
  36. }

ok,这里返回一个序列的问题也解决了。

结论3:在并行循环内重复操作的对象,必须要是thread-safe(线程安全)的。集合类的线程安全对象全部在System.Collections.Concurrent命名空间下。

四、返回集合运算结果/含有局部变量的并行循环

代码语言:txt
AI代码解释
复制
    使用循环的时候经常也会用到迭代,那么在并行循环中叫做 含有局部变量的循环 。下面的代码中详细的解释,这里就不啰嗦了。

Code

  1. /// <summary>
  2. /// 具有线程局部变量的For循环
  3. /// </summary>
  4. private void Demo9()
  5. {
  6. List<int> data = Program.Data;
  7. long total = 0;
  8. //这里定义返回值为long类型方便下面各个参数的解释
  9. Parallel.For<long>(0, // For循环的起点
  10. data.Count, // For循环的终点
  11. () => 0, // 初始化局部变量的方法(long),既为下面的subtotal的初值
  12. (i, LoopState, subtotal) => // 为每个迭代调用一次的委托,i是当前索引,LoopState是循环状态,subtotal为局部变量名
  13. {
  14. subtotal += data[i]; // 修改局部变量
  15. return subtotal; // 传递参数给下一个迭代
  16. },
  17. (finalResult) => Interlocked.Add(ref total, finalResult) //对每个线程结果执行的最后操作,这里是将所有的结果相加
  18. );
  19. Console.WriteLine(total);
  20. }
  21. /// <summary>
  22. /// 具有线程局部变量的ForEach循环
  23. /// </summary>
  24. private void Demo10()
  25. {
  26. List<int> data = Program.Data;
  27. long total = 0;
  28. Parallel.ForEach<int, long>(data, // 要循环的集合对象
  29. () => 0, // 初始化局部变量的方法(long),既为下面的subtotal的初值
  30. (i, LoopState, subtotal) => // 为每个迭代调用一次的委托,i是当前元素,LoopState是循环状态,subtotal为局部变量名
  31. {
  32. subtotal += i; // 修改局部变量
  33. return subtotal; // 传递参数给下一个迭代
  34. },
  35. (finalResult) => Interlocked.Add(ref total, finalResult) //对每个线程结果执行的最后操作,这里是将所有的结果相加
  36. );
  37. Console.WriteLine(total);
  38. }

结论4:并行循环中的迭代,确实很伤人。代码太难理解了。

五、PLinq(Linq的并行计算)

代码语言:txt
AI代码解释
复制
       上面介绍完了For和ForEach的并行计算盛宴,微软也没忘记在Linq中加入并行计算。下面介绍Linq中的并行计算。

4.0中在System.Linq命名空间下加入了下面几个新的类:

说明

ParallelEnumerable

提供一组用于查询实现 ParallelQuery{TSource} 的对象的方法。这是 Enumerable 的并行等效项。

ParallelQuery

表示并行序列。

ParallelQuery<TSource>

表示并行序列。

原理2:PLinq最多会开启64个线程

原理3:PLinq会自己判断是否可以进行并行计算,如果不行则会以顺序模式运行。

原理4:PLinq会在昂贵的并行算法或成本较低的顺序算法之间进行选择,默认情况下它选择顺序算法。

在ParallelEnumerable中提供的并行化的方法

ParallelEnumerable 运算符

说明

AsParallel()

PLINQ 的入口点。指定如果可能,应并行化查询的其余部分。

AsSequential()

指定查询的其余部分应像非并行 LINQ 查询一样按顺序运行。

AsOrdered()

指定 PLINQ 应保留查询的其余部分的源序列排序,直到例如通过使用 orderby 子句更改排序为止。

AsUnordered()

指定查询的其余部分的 PLINQ 不需要保留源序列的排序。

WithCancellation()

指定 PLINQ 应定期监视请求取消时提供的取消标记和取消执行的状态。

WithDegreeOfParallelism()

指定 PLINQ 应当用来并行化查询的处理器的最大数目。

WithMergeOptions()

提供有关 PLINQ 应当如何(如果可能)将并行结果合并回到使用线程上的一个序列的提示。

WithExecutionMode()

指定 PLINQ 应当如何并行化查询(即使默认行为是按顺序运行查询)。

ForAll()

多线程枚举方法,与循环访问查询结果不同,它允许在不首先合并回到使用者线程的情况下并行处理结果。

Aggregate() 重载

对于 PLINQ 唯一的重载,它启用对线程本地分区的中间聚合以及一个用于合并所有分区结果的最终聚合函数。

下面是PLinq的简单代码

Code

  1. /// <summary>
  2. /// PLinq简介
  3. /// </summary>
  4. private void Demo11()
  5. {
  6. var source = Enumerable.Range(1, 10000);
  7. //查询结果按source中的顺序排序
  8. var evenNums = from num in source.AsParallel().AsOrdered()
  9. where num % 2 == 0
  10. select num;
  11. //ForAll的使用
  12. ConcurrentBag<int> concurrentBag = new ConcurrentBag<int>();
  13. var query = from num in source.AsParallel()
  14. where num % 10 == 0
  15. select num;
  16. query.ForAll((e) => concurrentBag.Add(e \* e));
  17. }

上面代码中使用了ForAll,ForAll和foreach的区别如下:

PLinq的东西很繁杂,但是都只是几个简单的方法,熟悉下方法就好了。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-01-20 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
.NET Core 3.0之深入源码理解ObjectPool(一)
对象池是一种比较常用的提高系统性能的软件设计模式,它维护了一系列相关对象列表的容器对象,这些对象可以随时重复使用,对象池节省了频繁创建对象的开销。
AI.NET 极客圈
2019/10/15
5000
C# 多线程七之Parallel
关于Parallel不想说太多,因为它是Task的语法糖,至少我是这么理解的,官方文档也是这么说的,它本身就是基本Task的.假设我们有一个集合,不管是什么集合,我们要遍历它,首先想到的是For(如何涉及到修改或者读可以用for)或者Foreach(如果单纯的读),但是它两是同步的去操作集合,但是使用Parallel的静态For或者Foreach那就可以让多个线程参与这个工作,这样就能充分的利用CPU,但是你需要考虑CPU上下文产生的性能消耗,以及Parallel本身的性能消耗,所以,这也能解释为什么,你的循环里面执行的是不耗时的操作,使用for或者foreach的速度比使用Parallel的要快,所以使用Parallel还是要慎重.而且使用Parallel还需要注意的一点就是,不能有多线程争用问题,就是你的循环体里面不能有操作静态资源的操作.如果真的需要,那你可以加锁,但是那就失去它的优势了.
郑小超.
2018/12/24
1.4K0
C# 并发安全集合ConcurrentBag取代List
List集合是非线程安全的,所以我们这里了解下安全集合ConcurrentBag。 控制台测试程序: using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; namespace MyConcurrent
跟着阿笨一起玩NET
2020/03/18
3.5K0
C#并行与多线程——Parallel并行
硬件线程又叫做逻辑内核,我们可以在”任务管理器“中查看”性能“标签页,就能查看电脑的线程数,我们常说的四核八线程,就是指这个,当然这里的四核八线程,其中的八线程是超线程技术,也就是一个核心对应两个线程,从而从硬件层面提升执行性能。
李郑
2019/12/09
5.5K0
平行运算:Parallel.For、Parallel.Foreach的体验式试用[通俗易懂]
在编程里面我们经常会遇到编历一个列表或数组做同一件事情或操作,当这个数组或列表很大时又或是需要进行很复杂的操作时,就会花费很长的时间。以前我就在想能不能在这种情况下使用多线程的方式提高效率,可惜一直都没机会和动力(实际需要)去研究。今天在网上查找资料,很偶然的发现.NET Framework 4.0中平行算法相关内容(Parallel.For、Parallel.Foreach),原来.NET已经实现这项功能而且语法简化的异常简单。具体内容请大家查阅参考资料,下面将贴出我的测试结果与大家共享。
全栈程序员站长
2022/09/09
8140
.NET 各版本多线程使用原理与实践
多线程编程是现代应用程序开发中的核心技术,尤其是在需要并发处理或提升性能的场景中。本文将以 .NET 各版本为背景,详细探讨多线程技术的发展、底层原理以及实践方法。
Michel_Rolle
2024/11/19
2.1K0
Thread、ThreadPool、Task、Parallel、Async和Await基本用法、区别以及弊端
ThreadPool是Thread的一个升级版,ThreadPool是从线程池中获取线程,如果线程池中又空闲的元素,则直接调用,如果没有才会创建,而Thread则是会一直创建新的线程,要知道开启一个线程就算什么事都不做也会消耗大约1m的内存,是非常浪费性能的,接下来我们写一个例子来看一下二者的区别:
AI.NET 极客圈
2019/08/14
1.8K0
浪客剑心:位图法Bitmap算法分析
看了一篇文章《一道腾讯前端试题,谁来试试身手》,正好以前了解过位图法,确实不错。位图法适用于大规模数据,但数据状态又不是很多的情况。通常是用来判断某个数据存不存在,如可标记1为存在,0为不存在。  位图法网上资料比较少,我在百科找到了对它的描述 位图法比较适合于如下这种情况,它的做法是按照集合中最大元素max创建一个长度为max+1的新数组,然后再次扫描原数,遇到几就给新数组的第几位置上1,如遇到 5就给新数组的第六个元素置1,这样下次再遇到5想置位时发现新数组的第六个元素已经是1了,这说明这次的数
用户1161731
2018/01/11
1.2K0
浪客剑心:位图法Bitmap算法分析
C# 中的字符串内插 $对比string.Format
原文:https://blog.csdn.net/HeBizhi1997/article/details/123544524
_一级菜鸟
2023/10/23
2800
C# 中的字符串内插 $对比string.Format
并行编程和任务(一)
  并发、并行。同步、异步、互斥、多线程。我太难了。被这些词搞懵了。前面我们在写.Net基础系列的时候写过了关于.Net的异步编程。那么其他的都是些什么东西呀。今天我们首先就来解决这个问题。把这些词搞懂搞透。理清逻辑。然后最后我们进入并行编程的介绍。
小世界的野孩子
2019/11/07
9240
并行编程和任务(一)
Top 15 不起眼却有大作用的 .NET功能集
目录 1. ObsoleteAttribute 2. 设置默认值属性: DefaultValueAttribute 3. DebuggerBrowsableAttribute 4. ??运算符 5. Curry 及 Partial 方法 6. WeakReference 7. Lazy 8. BigInteger 9. 非官方关键字:__arglist __reftype __makeref __refvalue 10. Environment.NewLine 11. ExceptionDispatch
葡萄城控件
2018/01/10
7350
Top 15 不起眼却有大作用的 .NET功能集
.NET Thread、Task或Parallel实现多线程的使用总结
多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。
郑子铭
2023/08/30
3340
.NET Thread、Task或Parallel实现多线程的使用总结
【深入浅出C#】章节 9: C#高级主题:多线程编程和并发处理
多线程编程和并发处理的重要性和背景 在计算机科学领域,多线程编程和并发处理是一种关键技术,旨在充分利用现代计算机系统中的多核处理器和多任务能力。随着计算机硬件的发展,单一的中央处理单元(CPU)已经不再是主流,取而代之的是多核处理器,这使得同时执行多个任务成为可能。多线程编程允许开发人员将一个程序拆分成多个线程,这些线程可以并行执行,从而提高程序的性能和响应速度。 为什么多线程在现代应用中至关重要?
喵叔
2023/08/26
4.9K0
Parallel线程安全问题
废话不多说,上代码: using System; using System.Collections.Generic; using System.Threading.Tasks; namespace ParallelTest { class Program { static void Main(string[] args) { List<Product> products = new List<Product>();
圣杰
2018/03/28
9740
Parallel线程安全问题
C#:数据并行
在 Action<int, ParallelLoopState>等这样的action中,使用如下的代码可以实现stop和break:
sherlock99
2018/07/24
7410
C# Parallel 类指南
Parallel是.NET中的一个类,用于简化并行编程。它提供了一组方便的方法,帮助开发人员在多核处理器和多线程环境下执行任务,从而加速应用程序的执行。Parallel类可以自动将任务分成更小的子任务,并在多个线程上并行执行这些子任务,从而使利用多核处理器的能力变得更加容易和有效。Parallel还提供了一套机制来处理任务的取消和异常,减少了线程管理的复杂性,让开发人员更专注于任务本身。
Power
2025/04/02
460
使用.NET并行任务库(TPL)与并行Linq(PLINQ)充分利用多核性能
记录一下前段时间用到的.NET框架下采用并行策略充分利用多核CPU进行优化的一个方法
GuZhenYin
2024/09/26
2350
使用.NET并行任务库(TPL)与并行Linq(PLINQ)充分利用多核性能
C#中如何使用Parallel.For和Parallel.ForEach
利用C#中的无锁,线程安全的实现来最大化.NET或.NET Core应用程序的吞吐量。
苏州程序大白
2021/08/13
6.2K0
C#中如何使用Parallel.For和Parallel.ForEach
.NET Core多线程 (4) 锁机制
通过ILSpy反编译查看可以知道,lock是个语法糖,编译后其实是Monitor.Enter 和 Monitor.Exit 的封装。
Edison Zhou
2023/08/13
4310
.NET Core多线程 (4) 锁机制
C#如何:编写简单的 Parallel.ForEach 循环
本文档使用 lambda 表达式在 PLINQ 中定义委托。 如果不熟悉 C# 或 Visual Basic 中的 lambda 表达式,请参阅 PLINQ 和 TPL 中的 Lambda 表达式。
全栈程序员站长
2022/09/09
1.6K0
推荐阅读
相关推荐
.NET Core 3.0之深入源码理解ObjectPool(一)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档