简析C#多核并行编程

2017-03-10 03:02张文培宋玉贵潘锦莉
网络安全技术与应用 2017年3期
关键词:内核线程代码

◆张文培 宋玉贵 潘锦莉



简析C#多核并行编程

◆张文培 宋玉贵 潘锦莉

(西安工业大学 陕西 710000)

随着处理器多核化的不断发展,计算机的硬件计算能力在不断加强,软件要充分利用计算机硬件技术带来的好处,这就促成了并行计算成为软件编程的主流。C#支持并行编码,并且.NET Framework 4.0的并行扩展将并行编程的构造单元从线程提升到任务,极大程度地降低了编程难度,提升了编程效率。

多核;并行编程;任务;.NET

0 引言

多核处理器的出现使得传统的串行编程模式无法利用多核、多处理器的优势,随着多核、多处理器平台的出现,多核编程也得到了更深层次的发展,而现在并行编程技术的发展并不能完全利用多核的优势,因而寻求新的并行编程技术是十分必要的[1]。

C#是.NET平台中引入完全面向对象的编程语言,是C/C++的衍生语言,不但将C/C++的强大功能继承下来,并且去掉了它们的一些复杂特性,是一种能快速高效地实现基于.NET平台软件开发的编程语言,且C#已经支持并发代码,这对于实现C#多核并行编程是十分必要且有利的。

1 多核并行编程概述

1.1 多核环境

随着社会数字化、信息化的不断发展,对于计算机对数据的处理速度要求越来越快,在现代计算机技术的不断发展下,多核处理器已经成为计算机的处理器的主流,现代计算机大多都至少有一个双核的微处理器。

多核处理器是将两个及其以上的具有完全功能的核心集成在同一个芯片上,并将整个芯片作为整体对外输出[1]。多核处理器所集成的核心可以是单线程处理核心或多线程处理核心,集成后的多核处理器可同时执行的线程数或任务书是原本单处理器的数倍,这为处理器实现更高程度的并行提供了可能。并且,由于所有的核心都集成在片内,这就缩短了核心之间通信更高效,缩短核间的通信延迟,数据传输带宽也得到了提高[2]。除此之外,由于多核处理器的集成性,功耗降低,提高了其片上资源的利用率。由于多核处理器结构简单,易于优化,扩展性强,这些特点使得多核的发展并逐渐取代单处理器成为主流[3]。

1.2. 并行编程

传统的串行代码是顺序执行,且只用一个处理器,但随着现在多核的普及,串行程序无法充分利用多核的特点,这就促进了多核并行程序设计的出现。并行程序设计是指同时对多个任务、多条指令或多个数据项同时进行处理,是程序设计的一种形式,能够充分利用底层硬件所提供的并行执行能力从而提高程序的运行效率。

并行程序设计的重点就是要找到程序中的并行性,尽可能降低由于并行化引起的开销[4]。有时,并行化并不是优化算法的最佳选择,如果相比于串行执行的方式,并行化能够带来显著地性能提升,那么并行化才有意义。判断是否适合并行化并没有一劳永逸的方法——一切都取决于特定问题的功能需求和性能需求。在对现有程序进行并行优化的时候,必须理解现有的串行设计,或者理解提供了有限可扩展性的并行化算法,然后再对现有设计进行重构,从而使其获得性能提升,而且不会引入问题或产生不同的结果。在衡量程序并行化的效果时,可以用加速比来比较并行效果。其中,是加速比,是串行运算时间,也可表示为单处理器运行时间;是在有个核的处理器上的运行时间。

1.3 NET Framework 4

在传统的顺序式代码中,指令一条接着一条运行,这种方式并不能发挥多内核的优势,因为顺序指令只能运行在一个可用内核上。因此,在多核环境下,软件设计和程序编写能够准备好充分发挥多核系统的功能是一件十分重要的事情,但使用Visual C# 2010编写的顺序代码并不能发挥多核的优势,除非利用.NET Framework4所提供的新功能将任务分解到多个内核上,Visual C# 2010和.NET Framework 4使得将基于任务的设计转换为并行化代码变得非常简单。

NET Framework 4.0可实现创建并行代码新模型,这个新模型称为轻量级并发,这个模型减少了在不同逻辑内核上创建和执行代码所需要的总开销。这并不是说能够完全消除并行化带来的开销,但是这个模型本身是为现代多核微处理器而设计的。重量级并发模型是在多处理器的时代出现的,在那个时代计算机可能有很多物理微处理器,每个微处理器只有一个内核。轻量级的并发模型考虑了新的微架构,这个架构中有很多由一些物理内核支撑的逻辑内核。轻量级并发模型并不只是关注不同逻辑内核之间的作业调度,它还在框架级别添加了对多线程访问的支持,从而使得代码更容易理解。为了应对多核和众核的负载型,.NET Framework引入了新的Task Parallel Library(任务并行库,TPL),TPL是在多核时代应运而生的,TPL提供了一个轻量级的框架,可以让开发人员可以应付各种不同的并行场合。

2 C#并行编程

TPL引入了一个新的命名空间System.Threading.Task.通过这个命名空间可以引用.NET Framework 4中并行编程所需要的类、结构和枚举[5]。

TPL支持的数据场合主要包括以下三种。

(1)数据并行(data parallelism)

需要有大量数据需要处理,而且必须对每一份数据执行相同的操作。

(2)任务并行(task parallelism)

有很多可以并发运行的不同操作,通过任务并行发挥并行化的优势。

(3)流水线(pipelining)

是任务并行和数据并行的结合体。

2.1 Parallel

C#实现并行编程最简单、最基础的方法就是利用Parallel类,在编写并行代码的时候通过使用Parallel静态类(System.Threading.Tasks.Parallel)所提供的方法实现并行循环。

2.1.1 Parallel. Invoke、Parallel.For、Parallel.ForEach

在Parallel下面有三个常用的方法Invoke,For和ForEach。在需要使用的时候,引入命名空间System.Threading.Tasks,即可直接编辑Parallel.Invoke、Parallel.For、Parallel.ForEach指令。

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

Parallel.ForEach——为固定数目的独立For Each循环迭代提供了负载均衡的潜在的并行执行。这个方法支持自定义的分区器,可以更好地掌握数据分发。

Parallel.Invoke——对给定的独立任务提供潜在的并行执行。

2.1.2 Parallel退出循环和异常处理

(1)退出并行循环

在串行循环中,想要退出循环,直接使用break语句即可直接退出。但在并行循环中,并行循环参数提供了一个ParallelLoopState,可用于终止Parallel.For和Parallel.ForEach。ParallelLoopState的loopState实例提供了Break和Stop两种方法。

Break——通知并行计算尽快退出循环,在完成当前迭代后尽快地停止执行。

Stop——通知并行计算立即停止执行。

(2)并行循环的异常处理

在串行循环中,普通的Exception就可捕获异常,但由于在并行计算过程中,多个迭代在同时进行,就会产生N个异常并行出现,传统的异常管理方式已经不能满足,为并行计算而产生的新的System.AggregateException即可捕获到并行计算中异常。AggregateException包含了一个或多个在并行和并发代码执行过程中产生的异常[4]。

2.2 Task

处理并行计算时,通常使用的方式就是多线程,在.NET Framework 4之前使用最多的就是Thread,通过创建多个线程或者利用线程池的方法来实现并行计算。但在.NET Framework 4之后,TPL引入了新的基于任务的编程模型,这种编程模型同样可以充分发挥多核的优势,优化程序,不需要编写底层、复杂且重量级的线程代码,创建任务比创建线程具有更小的性能开销。但是任务和线程之间还是有区别的。

(1)任务是架构在线程之上的,即任务运行的时候还是需要线程实现。

(2)任务与线程之间并不是一对一的关系,一个线程可以运行多个任务,一个任务也可由多个线程完成。

(3)创建任务相较于创建线程而言开销更小。

2.2.1 Task的生命周期

Task的初始状态:

Created:利用构造函数创建Task实例时的初始状态,利用Task.Factory.StartNew创建时直接跳过。

WaitingToRun:利用Task.Factory.StartNew进行创建时的初始状态。

RanToCompletion:任务执行完毕。

创建Task的方法有两种:

(1)利用构造函数

vartask1=new Task()(=>{ });

(2)利用Task.Factory.StartNew进行创建

Var task2=Task.Factory.StartNew()(=>{ });

2.2.2 Task的任务控制

Task的特点就在于其任务控制部分,编程人员可以通过调整Task的执行顺序,从而实现Task的有序工作,Task提供了以下几种方法。

(1)Task.Wait()——当前线程一直等待任务执行完成后执行。

(2)Task.WaitAll()——当前线程会等待Task实例以Task数组的形式作为参数被接受,Task之间通过逗号隔开,这个方法是同步执行的。

(3)Task.ContinueWith()——第一个Task完成后自动启动下一个Task,实现Task的延续。

(4)Task的取消

Task是并行计算的,在.NET Framework 4.0中提供了一个取消标记(CancellationTokenSource.Token),在创建task的时候传入此参数,就可以将主线程和任务相关联,然后在任务中调用ThrowIfCancellationRequested方法来等待主线程使用Cancel来通知,一旦cancel被调用,task将会抛出OperationCanceledException来中断此任务的执行,Task实例会转入TaskStatus.Canceled状态,并且IsCanceled属性才回被设置为true。

2.2.3 Task的异常处理

当很多任务并行运行的时候,可能会并行发生很多异常。Task实例能够处理一组一组的异常,这些异常利用System.AggregateException类处理。初次之外,通过Task的属性也可判断Task的状态,例如:IsCompleted,IsFaulted,IsCancelled等。

3 总结

随着多核时代的到来,多核并行编程已然成为并行编程的主流模式,.NET Framework 4.0增加的TPL,很好地满足了多核处理器的应用要求。Parallel类和Task类的出现使得在编写并行计算的程序时,不需要考虑底层的运行机制,极大地简化了程序的复杂性,并且相较于之前通过创建多线程实现并行编程的方法而言,程序性能开销更小,更灵活。所以,在用C#实现多核并行编程时,可以更多地通过TPL的方式实现。

[1]彭晓明,郭浩然,庞建民.多核处理器——技术、趋势和挑战[J].计算机科学,2012.

[2]曹折波,李青.多核处理器并行编程模型的研究与设计[J].计算机工程与设计,2010.

[3]黄国睿,张平,魏广博. 多核处理器的关键技术及其发展趋势[J].计算机工程与设计,2009.

[4]高岚,王锐,钱德沛.多核处理器并行程序的确定性重放研究[J].软件学报,2013.

[5]Hillar G. Professional Parallel Programming with C#:Master Parallel Extensions with. NET 4[M].Wrox Press Ltd. 2010.

猜你喜欢
内核线程代码
多内核操作系统综述①
强化『高新』内核 打造农业『硅谷』
基于C#线程实验探究
活化非遗文化 承启设计内核
基于国产化环境的线程池模型研究与实现
线程池调度对服务器性能影响的研究*
Linux内核mmap保护机制研究
创世代码
创世代码
创世代码