.NET4.0并行计算技术基础(7)

 

19.3.4 任务并行库原理初探

         在上一小节中,我们看到只需简单地调用 Parallel 类中的一些静态方法,就可以让代码并行执行。您一定会对任务并行库的强大功能有了很深的印象,一些喜欢刨根问底的读者可能会问:
       任务并行库怎样实现代码的并行执行?
         任务并行库的底层技术细节很复杂,要介绍它超出了本书的范畴,然而,对其工作原理作一个介绍是可能的,了解这些知识,对于开发并行程序而言是很有益的。

1 并行指令的生成

        

  .NET4.0并行计算技术基础(7)_第1张图片
软件工程师使用 Paralllel 类编写的并行算法,经过编译器的处理,会全部转换为对 Task 类相应方法和属性的调用指令,这些指令被保存到编译好的程序集中。
         Task 类的实例代表一个可以被并行执行的任务, 任务(而不是线程!)是TPL 实现并行计算的基本单位。

2 任务并行库的工作原理

         任务由线程负责执行,为了获取较高的性能, TPL 使用线程池中的线程,并且使用了一个与线程池直接集成的“任务调度器( Task Scheduler ”来负责分派工作任务给线程, 这个调度器使用的任务分派策略称为“Work-stealing ”。
 
  .NET4.0并行计算技术基础(7)_第2张图片
 
         19 16 所示,线程池中的每个线程都拥有一个专有的(本地的)任务队列,当线程创建任务(即 Task 类的实例)时,默认设置下,这些任务被放入了线程本地工作队列中。
         如果任务本身是通过调用 ThreadPool.QueueUserWorkItem() 添加的,则此任务会被添加到一个全局队列( global queue )中,这一全局队列就是 19 16 中所示的“线程池任务队列”。
         以下是任务调度器实现任务调度的基本过程:
       当任务调度器开始分派任务时,它先检查一下创建此任务的线程是不是线程池中的线程(这种线程拥有一个本地的任务队列),如果不是,此任务被加入到线程池全局任务队列中,如果是,任务调度器检查此任务是否设置了TaskCreationOptions.PreferFairness 标记,如果设置了,则此任务被加入到线程池全局任务队列中,否则,还是被放入到线程的本地队列中。
       当一个线程开始执行时,它优先搜索自己的专有任务队列,当此队列为空时,它才会去搜索全局任务队列。由此可见,这种调度策略实际上是其于优先级的,本地工作队列比全局队列拥有更高的优先级。
         上述这种默认的调度策略适用于绝大多数情况,但不可能是所有的情况,如果需要对线程本地队列和线程池全局队列中的任务一视同仁,在不改变调度策略的情况下(这个策略是由 .NET 为线程池所提供的默认调度器实现的,不可改),可以通过将需要 一视同仁 Task 任务直接放到线程池全局队列而不是线程本地队列中实现,其具体的实现方法就是在创建任务时,设置它的 TaskCreationOptions.PreferFairness 标记。
 
      提示:
       如果并行执行是通过Parallel 类的InvokeForForEach 方法启动的,则不能为其指定TaskCreationOptions.PreferFairness 标记,只有在显式创建Task 类的代码中可以设置此标记。下一小节将介绍如何直接使用Task 类进行基于“任务”的并行编程。
 
         下面对任务并行库的工作原理作一个小结。
         简单地说: 线程就是 工人 ,它负责执行 任务 ,任务由任务调度器负责分配。
         任务调度器具有很强的智能性,它能自动协调各个任务的分配,不让 的线程 忙死 的线程 闲死 。从线程的角度看,由于有任务调度器的公平管理,所有线程都是 团结互助 雷锋
         将线程之间合作的工作从线程自身的职责中 剥离 出来,交由任务调度器来统一协调管理,这是 .NET 4.0 并行计算任务库设计的一个关键点。如果让线程自身来负责处理工作任务的合理分配,必然会在线程函数内增加同步的代码,这会让整个软件系统变得复杂和难于调试。
         我们可以适当地将 TPL 的这种设计思想引申到社会生活领域:如果将线程比喻为 政府官员 ,那么,任务调度器就可以看成是一种 制度 ,正是在 制度 的制约之下, 官员 才可能廉洁公正。
         在现实社会中,指望贪官他们 良心 发现而自己 金盆洗手 是不现实的,必须建立起一种有效的制度,让所有官员都置于强有力的监督之下, 贪污 的行为自然会受到极大的制约。这是题外话了。
         在下一小节中,我们将开始深入地了解 Task 类。

19.3.5 任务的创建与任务的状态

1 创建任务

19.3.3 节中,我们介绍了使用 Parallel 类的几个静态方法(如 Invoke For )进行并行编程的基本方法,在 19.3.4 节中,我们又知道了实际上 Parallel 类的功能是通过 Task 类实现的,因此,如果我们需要对任务的执行方式有更多的控制,可以直接基于 Task 对象编程而非使用 Parallel 类的静态方法。
进行并行编程的第一步,是创建一个任务对象。最简单的方法就是直接使用 new 关键字创建 Task 对象。 Task 类的构造函数有多个重载形式,我们逐个介绍其含义和用途:
 
    public Task(Action action);
 
         上述构造函数创建一个 Task 对象,并且让其关联一个任务函数(由 action 参数引用),当 Task 对象被线程执行时,此函数被调用。
 
public Task(Action<object> action, object state);
 
         这一构造函数的第 2 个参数用于向任务函数传送附加信息,这些附加信息其实就是任务函数调用时的实参。
 
public Task(Action action, TaskCreationOptions creationOptions);
 
         这一构造函数多了一个 TaskCreationOptions 类型的参数,此参数用于设置任务的属性标记,上一小节说过,默认情况下新建的任务会放在创建它的线程 [1] 的本地队列中,如果希望将任务放入线程池的全局队列中,可以向此构造函数传入“ TaskCreationOptions.PreferFairness ”值。

[1] 假设此线程是线程池中的线程
 
 
public Task(Action<object> action, object state,
    TaskCreationOptions creationOptions);
 
         这一构造函数是前 3 个构造函数的“集大成者”,各参数的含义不再赘述。
         总结一下, 每个任务一定关联有一个任务函数 这是 Task 对象的本质特征。
         创建好以后,并不会自动运行,必须显示调用它的 Start() 方法。只有此方法被调用之后,此任务才会被插入到线程(或线程池)所关联的任务队列中,并在任务调度器的管理下得到执行。
 
Task t = new Task(() =>
{
// 任务函数代码
});
// 任务对象创建完毕,但还未加入到任务队列中
t.Start(); // 将任务追加到相应的任务队列中调度执行。
 
         创建任务的第 2 种方法是使用 TaskFactory 类,顾名思义,此类是一个“任务创建工厂”,它提供了“一堆”的公有方法可用于创建任务对象。
         Task 类有一个静态属性 Factory 可用于引用一个 TaskFactory 对象。
         比如,上述创建并启动一个任务的代码可以简化为:
 
Task t = Task.Factory.StartNew(() =>
{
// 任务函数代码
});
 
         在深入了解 Task 类的基础之上, TaskFactory 类的使用就没有任何奇特之处,请读者自行查询 MSDN 了解 TaskFactory 类提供的另外一些方法的用法。

2 了解任务的状态

         “风萧萧兮易水寒,壮士一去兮不复还”,与线程对象一样,每一个 Task 对象都会经历一个生命周期,在这个生命周期的每个特定阶段,对象处于一个特定的状态,并且不可能由后一个状态“回转”到前一个状态。简单地说, Task 对象的生命是一条单行线,一旦上路,就只能往前走,直到生命的终结,期间绝无走回头路的可能。
19 17 所示, Task 对象拥有 8 个状态,这些状态之间可以相互转换。
         其中, Created 是起始状态,而 Canceled Faulted RanToCompletion 3 个终止状态,其余状态都是中间状态。
         通过对 Task 类特定的方法的调用, Task 对象会自动进行状态的转换。通常情况下软件工程师无需考虑这一转换过程,因为它们是由 TPL 基础架构直接管理的。
         Task 类提供了一个 Status 属性来表明当前对象所处的状态,但出于使用方便考虑, Task 类另外还提供了 3 个相关属性用于确定对象是否处理 3 个终止状态之一: IsCanceled IsFaulted IsCompleted

你可能感兴趣的:(.NET4.0并行计算技术基础(7))