写在前面

之前微信公众号里有一位叫sara的朋友建议我写一下Parallel的相关内容,因为手中商城的重构工作量较大,一时之间无法抽出时间。近日,这套系统已有阶段性成果,所以准备写一下Parallel的相关内容,正好也延续之前的C#并发编程系列。

Parallel是并行编程的相关内容,而Parallel.For和Parallel.Foreach又是并行编程中相当重要的方法,所以不能孤立的去讨论Parallel,必须要放到并行编程的讨论中去

并行化,一般是对所要完成的任务进行划分,并且以并发的方式处理属于自己的那份任务,并且最终可以做到整合,所以并行化总会产生并发性。

实际上并行是并发的子集,并发和并行都可以是多线程执行,就看其处理器是否是多核的,就看这些线程能不能同时被cpu多个核执行,如果可以就说明是并行,而并发是多个线程被cpu单核轮流切换着执行。总之,只有在多核处理器上并行才会有意义

并行的相关实战

说到并行,就需要先说下.NET FX4中引入的Task Parallel Library(任务并行库),简称TPL。TPL主要覆盖了三大使用场景,数据并行、任务并行和流水线,TPL以其高度的封装特性,隐藏了并行编程里复杂的处理,使得开发人员可以以较低的门槛进行并行编程。

数据并行

这种场景在于有大量数据需要处理,而且对每一份数据都要执行的同样的操作。

C#并发编程之初识并行编程-LMLPHP

任务并行

有很多相对独立的不同操作,或者可以分割成多个子任务但彼此之间是独立的,可以通过任务并行来发挥并行化的优势
C#并发编程之初识并行编程-LMLPHP

流水线

流水线是以上两种场景的结合,这个也是最复杂最难处理的场景,因为这里面涉及到多个并发的任务进行协调处理。

此场景,奈何小编理解的不是很好,所以不敢乱写,多方查找资料,找到了oschina上的一篇文章。

流水线技术,指的是允许一个机器周期内的计算机各处理步骤重叠进行。特别是,当执行一条指令时,可以读取下一条指令,也就意味着,在任何一个时刻可以有不止一条指令在“流水线”上,每条指令处在不同的执行阶段。这样,即便读取和执行每条指令的时间保持不变,而计算机的总的吞吐量提高了。
C#并发编程之初识并行编程-LMLPHP

原文地址:https://my.oschina.net/u/3374461/blog/1930305

System.Threading.Tasks.Parallel类

虽然Parallel类在System.Threading.Tasks命名空间下,但是创建并行代码不一定要直接使用Task类的实例,事实上我们可以直接使用Parallel静态类所提供的方法。

Parallel.For:为固定数目的独立For循环迭代提供了负载均衡式的并行执行

   1:  Parallel.For(0, 5, i =>
   2:  {
   3:      Console.WriteLine("the number is", i);
   4:  });

Parallel.Foreach:为固定数目的独立ForEach循环迭代提供了负载均衡式的并行执行。这个方法支持自定义分区器(Partitioner),以使得我们截图完全掌控数据分发。

   1:  string[] letters = new string[] {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M"};
   2:  Parallel.ForEach(letters, i => Console.WriteLine("letter is " + i));

Parallel.Invoke:为给定的独立任务提供了负载均衡式的并行执行,接下来会重点讨论这个方法。

Parallel.Invoke

这个方法很实用,也很简单。

以下代码可以返回void的无参数方法:

   1:  Parallel.Invoke(Method1(), Method2(), Method3(), Method4());

通过Lambda表达式运行:

   1:  Parallel.Invoke(() => Method1(), () => Method2(), () => Method3(), () => Method4());

通过Lambda表达式和匿名类型来运行:

   1:  Parallel.Invoke(() =>
   2:  {
   3:      Method1();
   4:      // Do something
   5:  }, 
   6:  () =>
   7:  {
   8:      Method2();
   9:      // Do something
  10:  }, 
  11:  () =>
  12:  {
  13:      Method3();
  14:      // Do something
  15:  }, 
  16:  () =>
  17:  {
  18:      Method4();
  19:      // Do something
  20:  });

以上代码需要并行执行四个方法,但是如果空余逻辑内核不足四个或者根本就没有四个逻辑内核,这四个方法是不能并发执行的。因此在理想情况下,正好有至少四个空余逻辑内核时,我们就可以并行执行这四个方法了。

Parallel.Invoke最大的优势就是简单,但是并不能因为它简单,就不分场合的使用,事实上,我们需要在某些场景下权衡使用。

  • 如果这四个方法的执行时间不一致,那么就需要根据最长的执行时间才能返回控制,这就可能造成一些逻辑内核处于闲置状态。所以我们需要预测一下大致的执行时间,如果时间过长,那么就要认真考虑是否真的需要使用这个方法。
  • 其扩展性很差,因为它只能调用固定数目的逻辑内核,剩余内核就会一直处于闲置状态。
  • 方法之间的交互极其困难,极易产生Bug,当然这是并行编程的常见问题,TPL也考虑到了这点,也有足够机制解决这个问题。
  • 如果其中某个方法有了异常,捕捉异常会很困难,所以需要大家在相应的被调用方法里编写足够的日志。
  • 小编在以前的使用中还遇到了内存溢出的异常,这些也会在以后的文章中说明其原因以及解决方法。

今天就写到这儿吧,已经十二点了,要休息一下,保护头发了,哈哈哈。

05-08 18:08