前 言
本书描述了并行程序设计中需要用到的模式,它们使用Microsoft .NET Framework 4中新加入的并行编程功能来进行并行设计,这种功能通常被称为并行扩展。你可以使用本书中的模式提高应用程序在多核计算机上的性能。在代码中使用这些模式能够使它们在现有机器上运行得更快,并且有助于适应将来的硬件环境——据预计,它们将融入越来越多的并行计算架构。
本书适合的读者
本书针对的读者对象是在Windows操作系统上利用.NET Framework编写托管代码的程序员。这包括在Visual C#、Visual Basic以及Visual F#上编写代码的程序员。本书不假定读者事先已了解并行编程技术。不过,读者需要熟悉C#的特征,如委托、lambda表达式、泛型以及语言集成查询(LINQ)表达式等。读者还应该大致了解进程和线程的概念。
请注意:本书中的示例是用C#写的,并且使用了.NET Framework 4中的特性,包括任务并行库(TPL)和并行LINQ(PLINQ)。不过,也可以将本书中呈现的概念应用到其他框架和库以及其他语言中。
完整的代码解决方案已经发布在CodePlex上,请参见http://parallelpatterns.codeplex.com/。每个示例都有一个C#版本。除此之外,还有Visual Basic版本和F#版本。
为何在此时写作本书
Visual Studio 2010开发系统实现了先进的并行编程功能,这使得并行程序设计的入门比以往任何时候都更加容易。
任务并行库(TPL)是为想要编写并行程序的.NET程序员而开发的。它简化了在应用程序中应用并行和并发的过程。TPL动态地调整并行度以最高效地利用所有可用的处理器。此外,TPL还能协助您在.NET线程池中划分和调度任务。该库提供了取消支持、状态管理以及其他服务。
并行LINQ(PLINQ)是LINQ to Object的并行实现。PLINQ实现了所有的LINQ标准查询操作符,将它们作为System.Linq命名空间的扩展方法,并额外加入了用于并行操作的操作符。PLINQ是声明式的高层次接口,能够用于查询筛选、投影及聚合等操作。
Visual Studio 2010包含了调试并行应用程序的工具。并行堆栈(Parallel Stack)窗口展示了应用程序中所有线程的堆栈调用信息。它能够让你自由地驰骋于各个线程与线程上的堆栈帧之间。并行堆栈窗口类似于线程窗口,只不过它显示的是每个任务的信息,而不是每个线程。通过Visual Studio分析器中的Concurrency Visualizer视图,可以看到应用程序如何与硬件、操作系统以及计算机上的其他进程交互。可以使用并发观测仪来定位性能瓶颈、处理器利用不足、线程争用、跨内核的线程迁移、同步延迟、I/O冲突区域以及其他信息。
想要了解Microsoft可利用的并行技术的概况,请阅读附录C。
使用代码的系统要求
本书使用的代码可以在http://parallelpatterns.codeplex.com/找到。以下是对系统的要求:
* Microsoft Windows Vista SP1、Windows 7、Microsoft Windows Server 2008或者Windows XP SP3 (32位或64位)操作系统
Microsoft Visual Studio 2010 (使用Concurrency Visualizer需要Ultimate 或Premium版本。可以通过Concurrency Visualizer来分析应用程序的性能),其中包含了运行示例代码所需的.NET Framework 4*
如何使用本书
本书从具体的模式出发讲述了并行编程技术。图0.1展示了各个不同的模式以及它们之间的关系。其中,数字代表该模式在本书哪一章中介绍。
图0.1 并行编程模式
在导论之后,本书从数据并行和任务并行两个方面讨论了并行编程技术。
并行循环和并行任务均运用程序的控制流作为协调和排列任务的方式。其他的模式则结合控制流和数据流两者来协调。控制流是指算法的步骤。数据流是指输入和输出的可用性。
导论
第1章阐述了意图使用并行技术来加快应用程序的运行速度的开发者们所面临的共同问题。本章解释了基本的概念,为后文做好铺垫。在1.2.4节中有一个表格可以帮助你选择适合你的应用的正确模式。
仅依赖控制的并行
第2章和第3章介绍了异步操作的排序仅受控制流限制的情况。
* 第2章介绍并行循环。想要在一个索引范围内或者一个集合的每个成员上执行相同的计算,并且成员之间没有依赖时,就使用并行循环。对于有依赖的循环,参见第4章。
* 第3章介绍并行任务。要执行几个不同的异步操作时,就使用并行任务。本章解释了为什么任务和线程适用于不同的目的。
依赖控制和数据的并行
第4章和第5章展示了既受控制流限制又受数据流限制的并发操作模式。
* 第4章介绍并行合并计算。并行合并计算模式适用于并行循环体中包含数据依赖的情况,例如计算总和或者在集合中查找最大值。
* 第5章介绍future模式。当操作产生的输出需要作为其他操作的输入时,就要用到future模式。操作的顺序受限于数据依赖的有向图。有些操作是并行执行的,也有些是串行的,取决于输入的可用性。
动态任务并行和流水线
第6章和第7章讨论了一些更高级的应用场景。
* 第6章介绍动态任务并行。在某些情况下,操作是在计算过程中动态地添加到待办任务中的。这种模式可应用于某些领域中,包括图形算法和排序。
* 第7章介绍流水线。使用流水线将一个组件的连续输出提供给另一个组件的输入队列。当流水线被充满,或者超过一个组件同时处于活跃状态时,则将结果并行化。
支撑材料
除了对这些模式的描述,本书还提供了几个附录。
* 附录A针对一些常用面向对象模式(例如外观模式、装饰模式和数据仓库)在多核体系中的适应性给出了一些提示。
* 附录B简要介绍了如何在Visual Studio 2010中调试和分析并行应用。
* 附录C描述了并行编程方面的各种微软技术和框架。
* 术语表。术语表包含了本书中用到的术语。
* 参考文献。参考文献列出了文中提到的著作。
读者应当首先阅读第1、2、3章,大致了解基本的原理。尽管后面的章节是以逻辑顺序安排的,但从第4章起,每章都可以独立阅读。
本书中的标注是以特定的方式展示的,提示你应当特别小心的地方。如此处边栏所示。
试图运用一种新的工具或技术来解决面临的所有问题,而不管其适用性,这种想法是很诱人的。正如俗语所说,“当你有一把锤子的时候,任何东西看起来都像是图钉。”这种“什么都是图钉”的心态可能会导致非常不幸的结果。显然,大家都希望图0.2中的小兔子能够避免这种结果。
你当然也想在并行编程中避免不幸的结果。在应用中增加并行性需要花费时间,且增加了复杂性。为了取得好的效果,应当只在效益大于投入的部分应用并行化。
图0.2 当你有一把锤子的时候,任何东西看起来都像是图钉
本书中没有包含的内容
比起I/O密集型的工作负载,本书更加关注的是计算密集型的负载。其目标是通过更好地利用计算机的可用内核,加快计算密集型的应用的运行速度。因此,本书没有花费太多的精力在I/O延迟上。不过,本书中也有关于负载均衡的讨论,且既包括计算密集型又包括I/O密集型(见第7章)。在第5章中还有一个关于用户界面的重要例子,该示例说明了带有I/O操作的任务的并发。
本书讨论了具有共享内存的单个多核节点中的并行性,而非集群中的并行,集群使用的是高性能计算(HPC)服务器的方法,在连通的节点上运用分布式存储。但是,想要在一个节点上利用并行优越性的集群程序员也可以在本书中找到有用的例子,因为集群的每个节点可以有多个处理单元。
目标
读完本书,你应当能够完成以下任务。
* 回答每章后面的问题。
* 寻找书中是否有你的应用程序所适合的模式,如果有,要知道是否有望完成一个简单的并行实现。
* 理解你的应用什么时候适合使用其中的一个模式。在这个时候,你需要进行更多的阅读和研究,或者寻求专家的帮助。
* 如果你的并行实现不能工作,能够知道可能的原因,例如任务之间的依赖冲突或者错误的共享数据。
利用“扩展阅读”一节找到更多的资料。*
[U1]不要盲目将本书中的模式运用到你的应用中。