第4讲 线程及其实现⚓
多处理器系统可分为共享存储系统和消息传递系统两类。
消息传递系统中,每个处理器都有一个只有它自己才能访问的局部存储器,处理器之间的通信必须通过消息传递进行。消息传递系统主要包括集群系统和异构计算机系统。
共享存储系统中,多个处理器共享公用的存储器,每个处理器都访问保存在共享存储器中的程序和数据,处理器之间通过存储器相互通信。进一步分 UMA 和 NUMA 结构。
- UMA(Uniform Memory Access 一致存储器访问)系统也称为 SMP(Symmetric Multi-Processor 对称多处理器)系统。所有的处理器没有主次或从属关系,所有处理器可以平等的访问存储器,共享相同的物理内存。
- NUMA(Non-Uniform Memory Access 非一致存储器访问)系统由多个节点构成,每个节点有自己的处理器和独立的存储器,节点间通过互联模块连接。共享存储器分布在所有处理机的本地存储器上,所有本地存储器的集合组成了全局地址空间,可被所有的处理机访问。
Intel i7-12700H
本人的笔记本电脑华硕天选3的处理器为 Intel i7-12700H,14核心20线程,属于混合架构处理器,基于 Alder Lake 架构。它有 6 个性能核心 P-Core 和 8 个效率核心 E-Core。P-Core 支持超线程技术,每个核心可模拟2个逻辑线程,一共提供12个线程;E-Core 不支持超线程技术,提供8个线程。因此总共有 20 个线程。
1. 线程的基本概念⚓
进程(process)是资源分配的单位,并发执行的进程不宜过多。
线程(thread)是 CPU 调度的单位,是进程中能够独立执行的实体(控制流)。线程也是进程的组成部分,同一进程中的不同线程共享进程的主存空间和资源。“进程 = 线程 + 资源平台”。
多线程(multi-threading):每个进程内允许包含多个并发执行的实体。在多线程环境下,进程是操作系统中除处理器外进行资源分配和保护的基本单位。进程拥有独立的虚拟地址空间,容纳进程映像(进程所关联的程序的代码与数据),并以进程为单位对各种资源实施保护,例如受保护地访问文件、外部设备及其他进程(进程间通信)。由于处理机调度的基本单位是线程,因此在创建一个进程时,同时要为该进程创建一个主线程。
线程共享进程的地址空间和资源:
- 进程级别资源(所有线程共享):address space, global variables, open files, child processes, pending alarms, signals and signal handlers, accounting information.
- 线程级别资源(每个线程独有):program counter, registers, stack, state.
线程的属性:执行状态(状态转换),上下文,执行栈(包括内核栈和用户栈),可存取所在进程的内存和其他资源,可以创建、撤消另一个线程。
每个线程有自己的栈:函数调用时传递参数,保存返回地址,存放局部变量。
线程控制块(TCB, thread control block)是管理线程的数据结构。TCB 中内容较少,因为多数信息已经记录在 PCB 中。
包含内容:线程标识、线程状态、调度参数、CPU 现场信息。其中,CPU 现场信息主要包括通用寄存器、指令计数器 PC 以及用户栈指针。OS 内核支持的线程的 TCB 中还包含系统栈指针。TCB 可能属于操作系统空间或用户进程空间,取决于线程的实现方式。
线程的状态也有运行、阻塞、就绪,转换方式类似于进程。挂起状态对线程没有意义,如果进程挂起后被对换出主存,则它的所有线程因共享了进程的地址空间,也必须全部对换出去,所以挂起状态是进程级状态,而非线程级状态。类似地,进程的终止会导致所有线程的终止。
进程与线程的比较
地址空间和其他资源:进程间相互独立,同一进程的各线程间共享,某进程内的线程在其他进程不可见。
通信:进程间通信采用 IPC 机制,线程间可以直接读写进程数据段(如全局变量)来进行通信,但是需要进程同步和互斥手段的辅助,以保证数据的一致性。
调度:线程上下文切换比进程上下文切换快得多。
线程的优点:
- 减小并发执行的时间和空间开销(线程的创建、退出和调度),因此容许在系统中建立更多的线程来提高并发程度。
- 创建/结束一个新线程花费时间少。
- 两个线程的切换花费时间少,如果机器设有保存/恢复所有寄存器指令,则整个切换过程用几条指令即可完成。
- 因为同一进程内的线程共享内存和文件,因此它们之间相互通信无须调用内核。
- 适合多处理机系统。
线程的缺点:
- 引入了一定的复杂性。
- 一个线程的崩溃可能影响到整个进程的稳定性。
- 线程能够提高的总性能有限,并且线程多了之后,线程本身的调度也更加复杂,需要消耗较多的CPU资源。
2. 线程的实现机制⚓
2.1 用户级线程⚓
User Level Thread, ULT.
线程管理工作由应用程序完成,在用户空间内实现。通过用户空间线程库(运行时系统)管理线程,操作系统内核不知道线程的存在,线程切换不需要核心态特权。线程库是一个管理 ULT 的例行程序包,实质上是线程的运行支撑环境。应用程序需通过调用线程库中的例程进行编程,与线程库链接后形成可执行程序,以实现多线程。
线程库的作用有:创建、撤销线程;在线程间传递消息和数据;调度线程执行;保护和恢复线程上下文。POSIX 的多线程编程接口为 pthread
。
每个进程有线程表(thread table)来跟踪进程中线程的状态,表项为线程控制块 TCB,对应一个线程,记录线程的 PC、栈、状态和寄存器等属性。线程表由线程库管理,当线程由运行态转为就绪态或阻塞态时,在 TCB 中存放重新运行该线程所需的信息。
用户级线程中,操作系统内核不知道线程的活动,仍然管理线程所从属的进程的活动,因此处理器调度的单位是进程而不是线程。线程状态是与进程状态独立的。当线程调用阻塞型系统调用时,整个进程阻塞。
用户级线程的优点:
- 线程切换不调用操作系统内核,性能良好。
- 调度是应用程序特定的,可针对应用优化。线程库的线程调度算法与操作系统的低级调度算法无关。
- 无论操作系统内核是否支持线程,ULT 都可以运行,只需要线程库即可,许多当代操作系统和程序设计语言均提供了线程库。
用户级线程的缺点:
- 调度通常采用非抢先式和更简单的规则。
- 大多数系统调用是阻塞的,因此内核阻塞进程,故进程中所有线程将被阻塞。
- 操作系统内核只将处理器分配给进程,同一进程中的两个线程不能同时运行于两个处理器上。
2.2 内核级线程⚓
Kernel Level Thread, KLT.
由内核管理线程。内核中需要有记录系统中所有线程的线程表,包括每个线程的线程控制块 TCB,由内核程序维护,一般用户程序不能访问。因此不需要线程库,进程中也不需要线程表。
内核级线程的优点:
- 多处理器上,内核能够同时调度同一进程中的多个线程并行执行。
- 阻塞在线程一级完成。若进程中的一个线程被阻塞,内核能够调度同一进程的其它线程占有处理器运行,也可以运行其它进程中的线程。
- 内核自身也可以用多线程技术实现,提高系统执行效率。
内核级线程的缺点:线程在用户态运行,调度和管理在内核实现,需要频繁进行模式切换。或者说,线程的创建、终止和切换都是通过系统调用,需要从用户态转换到内核态,由内核程序完成,系统开销较大。
2.3 混合实现⚓
使用内核级线程,然后将用户级线程与某些或全部内核级线程多路复用起来。内核只认识内核级线程并对其调度。某些内核级线程的顶部有若干可以轮流使用的用户级线程集合。
混合实现中,线程创建、调度、同步在用户级线程中完成,应用程序的多个 ULT 将被映射到一些 KLT 上。程序员可以根据情况调节 KLT 数量,以求系统达到最佳效果。
例如 Solaris 的线程模型,实现了三个层次的线程结构 ULT,LWP,KLT,有利于操作系统管理线程,并为应用程序提供了接口 API。LWP(Light Weight Process 轻量级进程)是一种由内核支持的用户线程。它是基于内核线程的高级抽象,因此只有先支持内核线程,才能有 LWP。每一个进程有一个或多个 LWP,每个 LWP 由一个内核线程支持,LWP 由内核独立调度,能够在多处理器中并发执行。LWP 可以支持多个 ULT,其作用是作为 ULT 和 KLT 之间的桥梁。
3. Linux 和 Windows 的线程⚓
略。