
1.5 进程、线程与处理器
现代处理器和进程、线程两个概念紧密关联。进程的概念简化了程序设计、存储器管理等,并且提供了一种大粒度并行的方法。线程存在进程之中,进程中的所有线程共享进程的资源并独享某些资源,因此更易于通信。本节介绍与此相关的进程、线程、超线程和处理器等概念及关系。
在实践中,进程可调度到一台机器中的一个或多个处理器核心上执行,而线程会调度到一个核心上执行,向量化的代码则会映射到一个核心内的向量单元上执行。由于操作系统调度策略的不同,并不能保证进程和线程会一直在相同的核心上执行。
通常基于进程的是像MPI一样的分布式存储器编程模式,基于线程的是像pthread、OpenMP等这样基于共享存储器的编程模式。由于分布式计算的各节点有其独立的存储器,因此基于进程的消息传递通信更适合,而多核等由于共享存储器,所以基于线程的共享存储器更易于通信。
1.进程
当程序在系统上运行时,操作系统会提供一种假象,就好像系统中只有这个程序在运行,只有该程序在使用核心、DRAM和设备。如果真的只有这个程序一直在使用核心的话,在单核心的系统上,用户就必须得等待当前正在执行的程序运行完才能输入下一条指令。现代操作系统并非这样,这是通过进程的概念实现的。
进程是对操作系统正在运行的程序的一种抽象。多个进程可以并发运行在一个核心上,通过时间片轮转,就好像进程一直在使用核心。系统内的多个进程通过时间片轮转并发执行,这就要求有一种机制能够在时间片到期时保存正在运行的进程的状态,并运行另一个等待运行的进程。这种保存一个进程的状态切换到另一个进程的过程称为“上下文切换”。
上下文是指保持进程运行所需要的寄存器、缓存和DRAM等资源。在任何一个无限精度的时刻,一个处理器核心最多只能运行一个进程或一个线程,当操作系统需要在某个核心上运行另一个进程时,就会进行上下文切换。
通过上下文切换进程获得了并发的特性,而运行在多个核心上的多个进程又获得了并行的特性,由于进程的上下文切换和通信比较耗时,因此基于进程的并发往往只适合于大粒度的任务并行。
进程和程序有关系也有不同,程序是静态指令的集合,进程是程序正在运行的状态。进程是资源拥有的独立单位,不同的进程拥有不同的虚拟地址空间,不能够直接访问其他进程的上下文资源。
2.线程
进程之中可以有许多线程,这些线程共享进程的上下文,如虚拟地址空间和文件,但是独立执行且可通过存储器进行通信。当进程终止时,进程内的所有线程也会同时终止。另外线程也有其私有逻辑寄存器、栈和指令指针PC。
由于线程共享进程资源,因此线程的建立、销毁比进程的建立、销毁更高效,在多核处理器上逐渐比进程更引人关注。
由于进程的存储器资源是独立的,而线程的存储器资源是共享的,因此通常基于进程的并行编程更简单,但是基于线程的并行在多核处理器上通常更高效。在多机系统中,不同的计算机天然适合多进程。因此在多核处理器上应优先选择线程级并行,而多机系统应选择进程级并行。实际上,许多现代系统及大规模程序充分利用这两种优势:在节点间使用进程级并行,在节点内的多核上使用线程级并行,这称为混合或超级并行。
目前基于GPU的并行编程也使用基于线程的开发环境,这是一种“硬件线程”,其线程的创建、调度和销毁开销接近0。
多线程程序在多核和单核上执行时具有明显的差别。由于在单核上多线程通过分时共享执行,这使得一些长延迟的操作如锁、IO访问不会导致核心空闲。事实上,网络服务器就是通过多线程技术来提升系统的吞吐量的。
3.超线程
Intel的一些高端机器支持称为“超线程”(Hyper-Threading,HT)的技术,超线程通过双倍增加一些资源(PC和寄存器)来减少线程的切换代价,但是只有一份执行单元,因此其峰值计算能力并没有提高。对于那些指令类型丰富且多的应用,超线程能够很好地提升性能。但是超线程不是万能的,在某些应用上性能可能会下降,而在绝大多数应用上提升不会超过20%。
超线程技术将一个物理处理器核模拟成两个逻辑核,可并行执行两个线程,能够在单个时针周期内在两个线程间切换,让单核都能使用线程级并行计算,减少了CPU的闲置时间,提高CPU的运行效率。采用超线程,应用程序可在同一时间里使用芯片的不同功能单元。单线程核心在任一时刻只能够对一条指令进行操作,而超线程技术可以让一个核心同时进行两个线程处理。
Intel表示,超线程技术让(P4)处理器在只增加5%芯片面积的情况下,就可以换来15%至30%的效能提升(实际上,对某些程序或非多线程程序而言,超线程反而会降低性能)。超线程技术需要主板芯片组和操作系统的配合,才能充分发挥效能。
4.阻塞和同步
在并行编程中,进程或线程的阻塞和非阻塞、同步和异步是非常常见的名词。准确地说,这4个名词有非常明显的区别,但是在某些文献中,阻塞与同步、非阻塞与异步的含义是一样的。
具体来说,阻塞是相对于进程或线程本身而言,如果一个操作并不阻碍进程或线程,接着执行代码,称这个操作为“非阻塞”,反之则为“阻塞”。相对非阻塞来说,阻塞更为常见,因为非阻塞要求开发人员手动保证操作的完成,这可能会带来数据一致性问题。
同步或异步则是针对通信的多个进程或线程,如果一个进程或线程与其他进程与线程通信时,不需要其他线程做好准备,称之为“异步”,反之则为“同步”。
阻塞和同步的具体含义可能会依据不同的编程环境、语言等有微小的不同。比如对于某些MPI异步数据传输函数实现来说,数据传输时传输的缓冲区和结果在异步操作返回时是否立即可用。