一篇围绕 Unix 线程基础展开的学习笔记。


进程基础 中,已介绍进程是资源分配与调度的基本单位。本篇聚焦线程的理论基础,不展开 pthread API:

  • 线程的定义及其与进程的关系
  • 并发与并行的区分及多线程的动机
  • 用户线程与内核线程的层次与映射
  • 经典线程模型(多对一、一对一、多对多)
  • 隐式多线程与线程撤销

线程的概念

1. 基本定义

线程是进程中的一条执行流,也是 CPU 调度和执行的基本单位。

一个线程至少包含线程 ID、程序计数器、寄存器现场和线程栈。这些构成线程的控制流状态——描述当前执行位置、下一条指令和函数调用现场。

组成部分作用
线程 ID线程的唯一标识
程序计数器指出下一条要执行的指令
寄存器现场保存当前计算状态
线程栈保存函数调用过程中的局部状态

线程不是独立于进程的完整资源容器,而是进程的实际执行者,因此也常被称为”执行线程”。

2. 线程与进程的关系

进程与线程的归属关系如下:

对象典型内容
进程地址空间、代码段、数据段、堆、打开文件、信号处理方式
线程程序计数器、寄存器集合、栈、调度状态

具体归属:

项目归属
地址空间进程共享
代码段 / 数据段 / 堆进程共享
打开的文件描述符进程共享
程序计数器线程专有
寄存器现场线程专有
线程栈线程专有
调度状态线程专有

进程上下文关注”这个运行实体拥有哪些资源”(地址空间、打开文件、信号处理方式),线程上下文关注”这条执行流当前执行到哪里”(程序计数器、寄存器、线程栈)。同进程内的线程切换只涉及线程上下文的替换;跨进程切换还需要切换进程上下文。

3. 单线程进程与多线程进程

类型特点
单线程进程进程内部只有一条执行流
多线程进程进程内部有多条执行流,共享同一组进程资源

单线程进程结构简单,但同一时刻只能沿一条路径推进。多线程进程将界面响应、网络 I/O、后台计算等任务拆到不同执行流,现代浏览器、编辑器、服务器普遍采用这种结构。

线程的作用

1. 并发与并行

并发 concurrency 与并行 parallelism 是两个不同概念:

概念含义关键点
并发多个任务在一段时间内都能取得进展强调”都在推进”
并行多个任务在同一时刻真正同时执行强调”绝对的同时执行”

硬件层次上,一个物理 CPU 封装(处理器)可包含多个核,每个核可通过硬件多线程暴露多个逻辑处理器:

概念含义
处理器 processor物理 CPU 封装,通常对应一个 CPU 插槽
core处理器内部可独立执行指令的计算单元
逻辑处理器 logical processor硬件多线程暴露给操作系统的执行上下文

操作系统看到的并行上限接近逻辑处理器数。在不同硬件结构下,并发/并行能力如下:

硬件结构同时执行线程数结果
单处理器、单核1仅能通过切换形成并发
单处理器、多核最多约等于核心数可并行
多处理器、多核最多约等于全部核心数并行能力更强

线程数增加不会带来无限线性加速。Amdahl 定律给出了上界:

Speedup(N)1S+1SN

其中 S 为程序中串行执行的比例,N 为可并行使用的处理单元数。若 S=0.25N=4,理论加速比上限仅约 2.29——多核与多线程确实能提升性能,但收益始终受串行部分限制。

2. 多线程的动机与优势

场景线程的作用直接收益
交互式程序同时存在界面刷新、用户输入、后台任务不同性质的工作拆到不同执行流响应性
服务器持续处理大量请求同一进程内并发处理多个连接或任务经济性、资源共享
计算任务可拆分到多个处理单元不同线程分担工作可伸缩性

多线程的核心优势:

优点含义
响应性某个耗时任务阻塞时,其他线程仍可继续响应
资源共享同一进程内线程天然共享地址空间和大部分资源
经济性创建和切换线程通常比进程更便宜
可伸缩性易于利用多核处理器并行执行任务

代价是竞争条件、同步复杂度和调试难度上升。

用户线程与内核线程

用户线程由应用程序或线程库在用户态组织,内核线程由内核调度器直接管理。两者不是并列的两类”线程品种”,而是处在不同层次——线程模型的核心问题就是二者的映射关系。

1. 层次关系

层次对象直接可见性主要作用
用户态用户线程应用程序、线程库组织程序内部执行流
内核态内核线程内核调度器真正参与 CPU 调度

若系统仅提供用户态线程库,多个用户线程在 CPU 调度上可能被视为一个线程——内核未必知晓它们每一个的存在。

2. 用户级线程

方面说明
管理位置用户空间线程库负责创建、切换和调度
优点不频繁陷入内核,创建和切换通常更快
局限阻塞系统调用可能拖住整个用户态调度,难以利用多核

用户线程由线程库在用户态调度——线程库决定”下一个让哪个用户线程运行”,与内核调度器分配 CPU 是两个独立层次。当多个用户线程复用同一个内核线程时,若该内核线程因阻塞系统调用进入等待态,线程库得不到 CPU,无法切换到同进程中的其他用户线程。

3. 内核级线程

方面说明
管理位置线程的创建、销毁和调度由内核参与
优点某线程阻塞时其他线程仍可运行,易于利用多核
代价管理成本高于纯用户级线程

内核知晓每个线程的存在:某线程因系统调用阻塞时,内核可将同进程的其他线程调度到 CPU 上运行;多核机器上,不同内核线程可分布到不同处理核并行执行。Linux、Windows、macOS 均支持内核级线程。

4. 对比

核心区别在于调度权的归属。

对比项用户级线程内核级线程
调度者用户态线程库内核调度器
内核是否可见不一定
阻塞影响可能拖住整个用户态调度通常只影响当前线程
多核利用受限容易并行
管理成本通常更低通常更高

线程模型

线程模型讨论用户线程与内核线程的映射关系。常见有 3 种。

1. 多对一模型

多个用户线程映射到一个内核线程。线程库在用户态本地切换,管理成本低;但从内核看,整个进程只对应一个可调度实体。若该内核线程因阻塞系统调用睡眠,所有用户线程均无法推进,且无法分配到多核并行执行。实现简单,但对现代多核系统不够友好。

2. 一对一模型

每个用户线程对应一个内核线程。一个线程阻塞时其他线程仍可运行,多核并行自然支持。代价:每创建一个用户线程即创建一个内核线程,线程数量多时内核管理成本上升。Linux 和 Windows 主要采用此模型。

3. 多对多模型

多个用户线程复用到多个内核线程上,试图结合前两者优点:用户态灵活管理大量线程,内核提供多个可调度执行实体。某个线程阻塞时,其他线程可复用别的内核线程继续执行。实现复杂度较高,因此现代主流系统更常见一对一模型。

三模型对比:

模型映射关系优点局限
多对一多用户线程→1 内核线程用户态开销低阻塞影响大,难利用多核
一对一1 用户线程→1 内核线程并发和并行能力强内核线程多,成本高
多对多多用户线程→多内核线程灵活,兼顾并发与效率实现复杂

4. 隐式多线程

隐式多线程将线程管理交给运行时系统:程序员描述任务,库或运行时决定如何创建、复用和调度线程。线程池是常见形式——预创建一组工作线程,从任务队列取任务执行,结束后归池复用,减少频繁创建/销毁开销并限制并发线程数。JVM 也是典型的隐式多线程代表。

方式程序员关注运行时负责
显式多线程创建线程、回收线程、安排同步较少
隐式多线程提交任务、描述并行工作线程创建、复用与调度策略

线程撤销

1. 基本概念

线程撤销 thread cancellation 指在线程正常完成之前提前终止它。例如,多线程并行搜索时,一个线程找到结果后其他线程即无继续运行的必要。被终止的线程称为目标线程 target thread

目标线程的撤销方式:

方式含义
异步撤销发出请求后目标线程立即终止
延迟撤销目标线程在安全位置检查请求,再有序结束自己

线程撤销伴随资源回收问题:线程可能正持有锁、修改共享数据或持有已分配资源,在不合适时机直接终止会导致程序状态不一致。

2. 异步撤销

异步撤销 asynchronous cancellation 发出请求后目标线程立即终止,不必等待配合。风险:

风险说明
资源未释放已申请的内存、文件、锁可能未正常清理
数据不一致修改共享数据中途被打断,破坏一致性
行为难预测线程断点不可控

3. 延迟撤销

延迟撤销 deferred cancellation 发出请求后,目标线程不立即终止,而是在撤销点 cancellation point 主动检查并有序退出——先完成必要的清理工作再结束自己。

对比项异步撤销延迟撤销
终止时机立即到达撤销点后
目标线程参与基本不参与主动检查并配合
资源清理风险高可控
程序状态较差更好

延迟撤销比异步撤销更安全、更常见。

小结

线程是进程内的执行流,也是 CPU 调度的基本单位。本篇的理论主线可以压缩如下:

主题核心内容
线程与进程的关系进程拥有资源,线程执行代码;同一进程内线程共享地址空间与大部分资源
并发与并行并发强调多任务推进,并行强调同一时刻真正同时执行
用户线程/内核线程用户线程由线程库管理,内核线程由调度器管理;调度权归属是关键区别
线程模型多对一、一对一、多对多,决定用户线程如何映射到内核线程
隐式多线程程序员提交任务,运行时负责线程创建与调度
线程撤销异步撤销(立即终止)与延迟撤销(在撤销点有序退出)

附注

1. JVM 隐式多线程

JVM 规范不规定 Java 线程如何映射到底层操作系统,由各 JVM 实现决定。Windows 采用一对一模式,每个 Java 线程映射到一个内核线程;Tru64 UNIX 等采用多对多模式。在实现上,Windows JVM 使用 Windows API 创建线程,Linux、Solaris、macOS 使用 Pthreads API。