不均和调度不规则。部分因素将在第8章中讨论,那时我们再来看一看影响并行代码性能的因素。无论应用程序在单核处理器,还是多核处理器上运行;也不论是任务切换还是真正的硬件并发,这里提到的技术、功能和类(本书所涉及的)都能使用得到。如何使用并发,将很大程度上取决于可用的硬件并发。我们将在第8章中再次讨论这个问题,并具体研究C++代码并行设计的问题。
线程是执行任务的最小单位,进程CPU核心是执行任务的基本单位。
即任务可以在一个CPU的线程下执行,而并行数量取决于CPU数。一个CPU同一时间只能运行一个线程(即只有一个线程处于运行态),两个CPU同时只能运行两个进程,两个线程,即最多两个任务。
1.1.2 并发的途径
试想当两个程序员在两个独立的办公室一起做一个软件项目,他们可以安静地工作、不互相干扰,并且他们人手一套参考手册。但是,他们沟通起来就有些困难,比起可以直接互相交谈,他们必须使用电话、电子邮件或到对方的办公室进行直接交流。并且,管理两个办公室需要有一定的经费支出,还需要购买多份参考手册。
假设,让开发人员同在一间办公室办公,他们可以自由的对某个应用程序设计进行讨论,也可以在纸或白板上轻易的绘制图表,对设计观点进行辅助性阐释。现在,你只需要管理一个办公室,只要有一套参考资料就够了。遗憾的是,开发人员可能难以集中注意力,并且还可能存在资源共享
的问题(比如,“参考手册哪去了?”)
以上两种方法,描绘了并发的两种基本途径。每个开发人员代表一个线程,每个办公室代表一个进程。
- 第一种途径
多进程并发
是每个进程只要一个线程,这就类似让每个开发人员拥有自己的办公室, - 第二种途径
多线程并发
是每个进程有多个线程,如同一个办公室里有两个开发人员。
让我们在一个应用程序中简单的分析一下这两种途径。
多进程并发
使用并发的第一种方法,是将应用程序分为多个独立的进程,它们在同一时刻运行,就像同时进行网页浏览和文字处理一样。
如图1.3所示,独立的进程可以通过进程间常规的通信渠道传递讯息(信号、套接字、文件、管道等等)。
(缺点)
不过,这种进程之间的通信通常不是设置复杂,就是速度慢
,这是因为操作系统会在进程间提供了一定的保护措施,以避免一个进程去修改另一个进程的数据。还有一个缺点是,运行多个进程所需的固定开销:需要时间启动进程,操作系统需要内部资源来管理进程,等等。
(优点)
当然,以上的机制也不是一无是处:
操作系统在进程间提供附加的保护操作和更高级别的通信机制,意味着可以更容易编写安全
的并发代码。实际上,在类似于Erlang(www.erlang.org/)的编程环境中,将进程作为并发的基本构造块。
使用多进程实现并发还有一个额外的优势———可以使用远程连接
(可能需要联网)的方式,在不同的机器上运行独立的进程。虽然,这增加了通信成本,但在设计精良的系统上,这可能是一个提高并行可用行和性能的低成本方式。
多线程并发
并发的另一个途径,在单个进程中运行多个线程。
线程很像轻量级的进程:
每个线程相互独立运行,且线程可以在不同的指令序列中运行。
(缺点)
但是,进程中的所有线程都共享地址空间,并且所有线程访问到大部分数据———全局变量仍然是全局的,指针、对象的引用或数据可以在线程之间传递。虽然,进程之间通常共享内存,但是这种共享通常是难以建立和管理
的。因为,同一数据的内存地址在不同的进程中是不相同。
图1.4展示了一个进程中的两个线程通过共享内存进行通信。
(优点)
地址空间共享,以及缺少线程间数据的保护,使得操作系统的记录工作量减小,所以使用多线程相关的开销远远小于使用多个进程
。
(缺点)
不过,共享内存的灵活性是有代价的:如果数据要被多个线程访问,那么程序员必须确保每个线程所访问到的数据是一致的(在本书第3、4、5和8章中会涉及,线程间数据共享可能会遇到的问题,以及如何使用工具来避免这些问题)。
问题并非无解,只要在编写代码时适当地注意即可,这同样也意味着需要对线程通信做大量的工作。
多个单线程/进程间的通信(包含启动)要比单一进程中的多线程间的通信(包括启动)的开销大,若不考虑共享内存可能会带来的问题,多线程将会成为主流语言(包括C++)更青睐的并发途径。此外,C++标准并未对进程间通信提供任何原生支持,所以使用多进程的方式实现,这会依赖与平台相关的API。因此,本书只关注使用多线程的并发,并且在此之后所提到“并发”,均假设为多线程来实现。
在多线程应用中,还有一种方式被广泛应用:并行
。 让我们来了解一下并发和并行的区别。
1.1.3 并发与并行
对于多线程来说,这两个概念有很大部分是重叠的。对于很多人来说,它们的意思没有什么区别。其区别主要在于关注点和意图方面(差距甚微)。这两个词都是用来对硬件在同时执行多个任务的方式进行描述的术语,不过并行更加注重性能。在讨论使用当前可用硬件来提高批量数据处理的速度时,我们会讨论程序的并行性;当关注的重点在于任务分离或任务响应时,就会讨论到程序的并发性。这对术语没有明显的区别,并且还有很多意义上的重叠。这两个术语存在的目的,就是为了区别多线程程序中不同的关注点。本书中,将有两个例子对并行和并发进行演示。了解并发后,让来看看为什么要使用并发。
1.2 为什么使用并发?
主要原因有两个:
关注点分离(SOC)
性能。
事实上,它们应该是使用并发的唯一原因;如果你观察得足够仔细,所有因素都可以归结到其中的一个原因(或者可能是两个都有。当然,除了像“我乐意”这样的原因之外)。