.Net新特性-并行计算应用指南1.特性简介1.1.并行编程体系结构许多个人计算机和工作站都有两个或四个内核(即CPU),使多个线程能够同时执行。在不久的将来,计算机预期会有更多的内核。为了利用当今和未来的硬件,您可以对代码进行并行化,以将工作分摊在多个处理器上。过去,并行化需要线程和锁的低级操作。VisualStudio2010和.NETFramework4提供了新的运行时、新的类库类型以及新的诊断工具,从而增强了对并行编程的支持。这些功能简化了并行开发,使您能够通过固有方法编写高效、细化且可伸缩的并行代码,而不必直接处理线程或线程池。下图从较高层面上概述了.NETFramework4中的并行编程体系结构。图1、并行编程体系结构1.2.并行计算组件图2、并行计算组件NET4.0并行计算组件主要包括以下几个部分:1并行语言集成查询(PLINQ,ParallelLanguageIntegratedQuery),这是.NET3.0引入的LINQtoObject的换代“产品”,让查询操作可以并行执行。2任务并行库(TPL,TaskParallelLibrary):将开发并行程序的抽象级别从“线程(thread)”提升到“任务(Task)”,只需规定好计算机要执行的任务,然后由.NET去管理线程的创建和同步等问题。3同步的数据结构(CDS,CoordinationDataStructures):包括一组线程安全的常用数据结构,比如线程安全的队列、堆栈等,在并行程序中访问这些数据结构,可以不需要显式地使用lock。4任务调度器(TaskScheduler):负责任务的创建、执行、暂停等管理工作。5线程池:.NET4.0对原有的托管线程池功能进行了大幅度的增强,通过给其集成一个任务调度器,线程池中的线程可以高效地并行执行各种任务。上述五个组成部分当中,PLINQ是建立在TPL之上的,而TaskScheduler是并行计算的核心,是一个Runtime,它与线程池相集成,负责将任务分派给线程池中的各个线程执行。2.任务并行库2.1.数据并行数据并行是指对源集合或数组中的元素同时(即并行)执行相同操作的情况。System.Threading.Tasks.Parallel类中For和ForEach方法的若干重载支持使用强制性语法的数据并行。在数据并行操作中,将对源集合进行分区,以便多个线程能够同时对不同的片段进行操作。TPL支持通过System.Threading.Tasks.Parallel类实现的数据并行。此类提供for和foreach循环(VisualBasic中为For和ForEach)基于方法的并行实现。为Parallel.For或Parallel.ForEach循环编写循环逻辑与编写顺序循环非常类似。您不必创建线程或队列工作项。在基本的循环中,您不必采用锁。TPL将为您处理所有低级别工作。下面的代码示例演示一个简单的foreach循环及其并行等效项。当并行循环运行时,TPL将对数据源进行分区,以便循环能够同时对多个部分进行操作。在后台,任务计划程序将根据系统资源和工作负荷来对任务进行分区。如有可能,计划程序会在工作负荷变得不平衡的情况下在多个线程和处理器之间重新分配工作。附件:数据并行代码示例2.2.任务并行顾名思义,任务并行库(TPL)基于任务的概念。术语“任务并行”是指一个或多个独立的任务同时运行。任务表示异步操作,在某些方面它类似于创建新线程或ThreadPool工作项,但抽象级别较高。任务提供两个主要好处:系统资源的使用效率更高,可伸缩性更好。在后台,任务排队到ThreadPool,ThreadPool已使用登山等算法进行增强,这些算法能够确定并调整到可最大化吞吐量的线程数。这会使任务相对轻量,您可以创建很多任务以启用细化并行。为了补偿这一点,可使用众所周知的工作窃取算法提供负载平衡。对于线程或工作项,可以使用更多的编程控件。任务和围绕它们生成的框架提供了一组丰富的API,这些API支持等待、取消、继续、可靠的异常处理、详细状态、自定义计划等功能。出于这两个原因,在.NETFramework4中,任务是用于编写多线程、异步和并行代码的首选API。附件:任务并行代码示例2.3.并行指令生成图3、并行指令软件工程师使用Paralllel类编写的并行算法,经过编译器的处理,会全部转换为对Task类相应方法和属性的调用指令,这些指令被保存到编译好的程序集中。Task类的实例代表一个可以被并行执行的任务,任务(而不是线程!)是TPL实现并行计算的基本单位。2.4.任务并行库工作原理任务由线程负责执行,为了获取较高的性能,TPL使用线程池中的线程,并且使用了一个与线程池直接集成的“任务调度器(TaskScheduler)”来负责分派工作任务给线程,这个调度器使用的任务分派策略称为“Work-stealing”。图4、线程调度如图4所示,线程池中的每个线程都拥有一个专有的(本地的)任务队列,当线程创建任务(即Task类的实例)时,默认设置下,这些任务被放入了线程本地工作队列中。如果任务本身是通过调用ThreadPool.QueueUserWorkItem()添加的,则此任务会被添加到一个全局队列(globalqueue)中,这一全局队列就是图4中所示的“线程池任务队列”。以下是任务调度器实现任务调度的基本过程:当任务调度器开始分派任务时,它先检查一下创建此任务的线程是不是线程池中的线程(这种线程拥有一个本地的任务队列),如果不是,此任务被加入到线程池全局任务队列中,如果是,任务调度器检查此任务是否设置了TaskCreationOptions.PreferFairness标记,如果设置了,则此任务被加入到线程池全局任务队列中,否则,还是被放入到线程的本地队列中。当一个线程开始执行时,它优先搜索自己的专有任务队列,当此队列为空时,它才会去搜索全局任务队列。由此可见,这种调度策略实际上是其于优先级的,本地工作队列比全局队列拥有更高的优先级。上述这种默认的调度策略适用于绝大多数情况,但不可能是所有的情况,如果需要对线程本地队列和线程池全局队列中的任务一视同仁,在不改变调度策略的情况下(这个策略是由.NET为线程池所提供的默认调度器实现的,不可改),可以通过将需要“一视同仁”的Task任务直接放到线程池全局队列而不是线程本地队列中实现,其具体的实现方法就是在创建任务时,设置它的TaskCreationOptions.PreferFairness标记。提示:如果并行执行是通过Parallel类的Invoke、For和ForEach方法启动的,则不能为其指定TaskCreationOptions.PreferFairness标记,只有在显式创建Task类的代码中可以设置此标记。下一小节将介绍如何直接使用Task类进行基于“任务”的并行编程。3.一个应用实例实例包括3个实体,订单(Order)、订单明细(OrderDetail)和订单汇总(OrderTotal),其中订单组合订单明细。实例模拟U9单据列表操作,并更新公共资源(订单汇总),每张单据保存和更新订单汇总为一个完整事务,且每次单独开启一个数据库连接和事务。附件:应用代码实例4.U9的应用方案4.1.列表批量操作U9列表批量操作包括:提交、审核、弃审、删除,按单据为单位进行事务隔离,循环调用实体修改BP,触发实体的相应事件进行相应的业务逻辑处理。因为每张单据都是独立事务,完全符合并行计算特性。但U9的事务是通过BP调用初始化的,BP的上下文与当前线程相关,所以在并行计算环境下,首先需要解决多线程下,每个线程的BP上下文,以及平台上下问题。本文以库存管理模块的单据列表批量操作为例逐一进行讲解。4.1.1.调用并行接口U9单据列表操作都是UI调用一个后台BP,后台BP根据每张单据循环调用更新单据小BP,每个小BP是独立事务。所以鉴于次,我们把并行计算接口进程封装,并行接口封装进了UBF.System.dll,开发部只需要把小BP的实例集合传给并行接口方法:List