基于任务的程序设计(14)

1.5  使用轻量级并发模型

Amdahl法则和Gustafson法则都没有考虑引入并行所带来的开销。这两个法则也没有考虑到存在一些模式能够将串行部分转换为能够充分利用并行化的新算法。减少应用程序中串行部分的代码对于提高并行执行单元的使用率而言非常重要。

在以前版本的.NET Framework中,如果想要在C#应用程序(一个进程)中并行地运行代码,那么您必须创建并管理多个线程(软件线程)。因此,您必须编写非常复杂的多线程代码。将算法分解为多个线程、协调各个代码单元、在代码单元之间共享信息以及收集运算结果等任务实在是非常复杂的程序设计工作。随着逻辑内核的增加,这个任务会变得更加复杂,因为您需要通过更多线程来获得更好的可扩展性。

多线程编程模型的设计并不是为了帮助开发人员面对多核革命。事实上,创建新的线程需要执行大量的处理器指令,而且可能会对已经分解为并行化线程的代码引入太大的开销。很多有用的数据结构和类在设计上并没有考虑到被多线程访问,因此,为了能够实现这一点还需要添加很多代码。这些额外的代码会使得开发人员的注意力偏离主要目标:通过并行执行来提升性能。

由于这个多线程模型过于复杂,难以应对多核革命,因此这个模型被称为重量级并发(heavyweight concurrency)。这个模型加入了严重的开销,需要编写很多代码来处理由于框架层次缺乏对多线程访问的支持而带来的问题,并且会导致代码复杂难以理解。

由于以前版本的.NET Framework所提供的多线程模型和现代微处理器日益增长的逻辑内核数引发的上述问题,促进了允许创建并行化代码的新模型的创建。这个新模型称为轻量级并发(lightweight concurrency),这个模型减少了在不同逻辑内核上创建和执行代码所需要的总开销。这并不是说能够完全消除并行化带来的开销,但是这个模型本身是为现代多核微处理器而设计的。重量级并发模型是在多处理器的时代出现的,在那个时代计算机可能有很多物理微处理器,每个微处理器只有一个内核。轻量级的并发模型考虑了新的微架构,这个架构中有很多由一些物理内核支撑的逻辑内核。

轻量级并发模型并不只是关注不同逻辑内核之间的作业调度,它还在框架级别添加了对多线程访问的支持,从而使得代码更容易理解。

大多数现代程序设计语言都在向轻量级并发模型变革,幸运的是,.NET Framework 4也加入了这一波变革。因此,所有能够生成.NET应用程序的托管语言都能够充分利用这种新的模型并发挥其优势。

1.6  创建成功的基于任务的设计

有时候,必须对现有的解决方案进行优化才能够充分利用并行化的优势。在这些情况下,您必须理解现有的串行设计,或者理解提供了有限可扩展性的并行化算法,然后再对现有设计进行重构,从而使其获得性能提升,而且不会引入问题或产生不同的结果。您既可以取问题的一小部分,或者也可以考虑整个问题,并创建出一个基于任务的设计,然后就可以引入并行化了。在设计新解决方案的时候也可以采取同样的技术。

遵循以下的步骤就可以创建出成功的基于任务的设计:

(1) 将每个问题分解为很多子问题,完全不要去考虑顺序执行。

(2) 将每个子问题想象为下面三类中的一类:

● 能够以并行的方式进行处理的数据——对数据进行分解以实现并行化。

● 需要很多任务,而且能够以某种复杂的并行化进行处理的数据流——对数据和任务进行分解以实现并行化。

● 可以并行运行的任务——对任务进行分解以实现并行化。

(3) 将设计组织为能够表达并行化的形式。

(4) 考虑将不同子问题连接起来的任务的必要性。尽可能地避免依赖性。

(5) 在进行设计的时候,心里要想着并发和潜在的并行化。

(6) 分析并行化的问题的执行计划,考虑当前的多核微处理器和未来的架构。在设计的时候要准备好更高的可扩展性。

(7) 尽可能减少临界区。

(8) 尽可能通过基于任务的程序设计实现并行化。

(9) 调优和迭代。

上述步骤并不是说所有的子问题都必须是运行在不同线程中的并行化任务。设计的时候必须考虑并行化的可能性,然后在编写代码的时候,可以根据性能和可扩展性的目标选择最佳的实现方式。重要的是要以并行方式进行思考,将要解决的工作分解为任务。通过这种方式,您就能够按照需要对代码进行并行化。如果您已经拥有一个面向传统串行执行的设计,那么您将需要努力通过基于任务的程序设计技术对现有设计进行并行化。

读书导航