1.1.1 计算机系统中的并发
若我们谈及计算机系统中的并发,则是指同一个系统中,多个独立活动同时进行,而非依次进行。这不足为奇。多年来,多任务操作系统可以凭借任务切换,让同一台计算机同时运行多个应用软件,这早已稀松平常,而高端服务器配备了多处理器,实现了“真并发”(genuine concurrency)。大势所趋,主流计算机现已能够真真正正地并行处理多任务,而不再只是制造并发的表象。
很久之前,大多计算机都仅有一个处理器,处理器内只有单一处理单元或单个内核,许多台式计算机至今依旧如此。这种计算机在同一时刻实质上只能处理一个任务,不过,每秒内,它可以在各个任务之间多次切换,先处理某任务的一小部分,接着切换任务,同样只处理一小部分,然后对其他任务如法炮制。于是,看起来所有任务都正在同时执行。因此其被称为任务切换。至此,我们谈及的并发都基于这种模式。由于任务飞速切换,我们难以分辨处理器到底在哪一刻暂停某个任务而切换到另一个。任务切换对使用者和应用软件自身都制造出并发的表象。由于是表象,因此对比真正的并发环境,当应用程序在进行任务切换的单一处理器环境下运行时,其行为可能稍微不同。具体而言,如果就内存模型(见第5章)做出不当假设,本来会导致某些问题,但这些问题在上述环境中却有可能不会出现。第10章将对此深入讨论。
多年来,配备了多处理器的计算机一直被用作服务器,它要承担高性能的计算任务;现今,基于一芯多核处理器(简称多核处理器)的计算机日渐普及,多核处理器也用在台式计算机上。无论是装配多个处理器,还是单个多核处理器,或是多个多核处理器,这些计算机都能真正并行运作多个任务,我们称之为硬件并发(hardware concurrency)。
图1.1所示为理想化的情景。计算机有两个任务要处理,将它们进行十等分。在双核机(具有两个处理核)上,两个任务在各自的核上分别执行。另一台单核机则切换任务,交替执行任务小段,但任务小段之间略有间隔。在图1.1中,单核机的任务小段被灰色小条隔开,它们比双核机的分隔条粗大。为了交替执行,每当系统从某一个任务切换到另一个时,就必须完成一次上下文切换(context switch),于是耗费了时间。若要完成一次上下文切换,则操作系统需保存当前任务的CPU状态和指令指针[2],判定需要切换到哪个任务,并为之重新加载CPU状态。接着,CPU有可能需要将新任务的指令和数据从内存加载到缓存,这或许会妨碍CPU,令其无法执行任何指令,加剧延迟。
图1.1 两种并发方式:双核机上的并发执行与单核机上的任务切换
尽管多处理器或多核系统明显更适合硬件并发,不过有些处理器也能在单核上执行多线程。真正需要注意的关键因素是硬件支持的线程数(hardware threads),也就是硬件自身真正支持同时运行的独立任务的数量。即便是真正支持硬件并发的系统,任务的数量往往容易超过硬件本身可以并行处理的数量,因而在这种情形下任务切换依然有用。譬如,常见的台式计算机能够同时运行数百个任务,在后台进行各种操作,表面上却处于空闲状态。正是由于任务切换,后台任务才得以运作,才容许我们运行许多应用软件,如文字处理软件、编译器、编辑软件,以及浏览器等。图1.2展示了双核机上4个任务的相互切换,这同样是理想化的情形,各个任务都被均匀切分。实践中,许多问题会导致任务切分不均匀或调度不规则。我们将在第8章探究影响并发代码性能的因素,将解决上述某些问题。
图1.2 4个任务在双核机上切换
本书涉及的技术、函数和类适用于各种环境:无论负责运行的计算机是配备了单核单处理器,还是多核多处理器;无论其并发功能如何实现,是凭借任务切换,还是真正的硬件并发,一概不影响使用。然而,也许读者会想到,应用软件如何充分利用并发功能,很大程度上取决于硬件所支持的并发任务数量。我们将在第8章讲述设计并发的C++代码的相关议题,也会涉及这点。