操作系统-线程
概述
线程(thread),又被称为轻量级进程(Lightweight Process, LWP)是程序执行流的最小单位,也是调度的基本单位。
为什么需要线程?
- 创建进程的开销较大:包括了数据、代码、堆、栈等
- 进程的隔离性过强:进程间交互可以通过进程间通信(IPC),但开销较大
- 进程内部无法支持并行
线程相较于进程,是一种更加轻量级的运行时抽象。
-
线程只包含运行时的状态:线程执行状态(运行、就绪等)、线程上下文(PC等寄存器集合)、栈。
-
一个进程可以包含多个线程:多个线程共享同一地址空间。
多线程进程的地址空间
- 每个线程拥有自己的栈。
- 内核中也有为线程准备的内核栈。
- 其它区域(数据、代码、堆)共享。
用户态线程与内核态线程
根据线程是否受内核管理,可以将线程分为两类:
- 内核态线程
- 内核可见,受内核管理
- 由内核创建,线程相关信息存放在内核中
- 用户态线程
- 内核不可见,不受内核直接管理
- 在应用态创建,线程相关信息主要存放在应用数据中
与内核线程相比,用户态线程更加轻量级,创建开销更小,但功能也较为受限,与内核态相关的操作(系统调用)需要内核态线程协助才能完成。
线程模型
线程模型表示了用户态线程与内核态线程之间的联系。
- 多对一模型:多个用户态线程对应一个内核态线程
- 一对一模型:一个用户态线程对应一个内核态线程
- 多对多模型:多个用户态线程对应多个内核态线程
指标 | 多对一模型 | 一对一模型 | 多对多模型 |
---|---|---|---|
概念 | 将多个用户态线程映射给单一的内核线程 | 每个用户线程映射单独的内核线程 | N个用户态线程映射到M个内核态线程(N > M) |
优点 | 高效的上下文切换和无限制的线程数量 内核管理简单 |
用户线程和内核线程一致 可扩展性好 |
解决了可扩展性问题(多对一)和线程过多问题(一对一) |
缺点 | 可扩展性差,无法适应多核机器的发展 一个用户线程阻塞,导致内核线程阻塞,其他线程也无法执行 |
os会限制内核线程数量,因此用户线程数量受到限制 内核线程上下文切换开销大 |
管理复杂 |
实际情况 | 在主流操作系统中被弃用 用于各种用户态线程库中 |
主流操作系统(Windows、Linux、OS X)都采用一对一模型 | 在虚拟化中得到了广泛应用 |
相关数据结构:
TCB 线程控制块
一对一模型的TCB可以分为两部分
- 内核态:与PCB结构类似
- Linux中进程与线程使用的是同一种数据结构(task_struct)
- 上下文切换中会使用
- 应用态:可以由线程库定义
- Linux:pthread结构体
- Windows:TIB(Thread Information Block)
- 可以认为是内核TCB的扩展
TLS 线程本地存储
不同线程可能会执行相同的代码(线程不具有独立的地址空间,多线程共享代码段)。
对于全局变量,不同线程可能需要不同的拷贝(用于标明系统调用错误的errno)。
使用TLS可以很方便的实现线程内的全局变量。
-
线程库允许定义每个线程独有的数据
- __thread int id; 会为每个线程定义一个独有的id变量
-
每个线程的TLS结构相似
- 可通过TCB索引
-
TLS寻址模式:基地址+偏移量
- X86: 段页式 (fs寄存器)
- AArch64: 特殊寄存器tpidr_el0
reference
[2] 操作系统导论(ostep)
[3] 操作系统-精髓与设计原理
[4] 现代操作系统
[5] 程序员的自我修养