Linux 线程概念

Linux 线程概念

七月 24, 2025 次阅读

线程的基本概念

线程是在一个程序里的一个执行路线,更准确的定义是:线程是”一个进程内部的控制序列”。一切进程至少都有一个执行线程,线程在进程内部运行,本质是在进程地址空间内运行。

在Linux系统中,从CPU的角度来看,线程对应的PCB(进程控制块)比传统的进程更加轻量化。通过进程的虚拟地址空间,我们可以看到进程的大部分资源,将这些资源合理分配给每个执行流,就形成了线程执行流。

thread_concept

线程与进程的关系

我们可以用一个家庭成员来形象地理解线程、执行流和进程之间的关系:

  • 进程就像一个家庭,拥有共同的资源(房子、存款、家具等)
  • 线程就像家庭中的各个成员(父母、孩子),共享家庭资源但各自有自己的任务
  • 执行流就像家庭成员各自的活动轨迹(爸爸上班、妈妈做饭、孩子学习)

资源分配与执行

  • 进程是承担分配系统资源的基本实体,它拥有独立的地址空间、文件描述符、信号处理等资源
  • 线程是进程内部的执行流单元,多个线程共享进程的资源,但每个线程有自己的执行上下文

线程的实现机制

分页式存储管理

现代操作系统使用虚拟内存和分页机制来管理内存:

  1. 将物理内存按照固定长度的页框(通常4KB)进行分割
  2. 为每个进程提供独立的虚拟地址空间(32位系统为0~4GB)
  3. 通过页表建立虚拟地址和物理地址的映射关系

这种机制允许:

  • 连续的虚拟内存映射到不连续的物理内存页
  • 解决物理内存碎片问题
  • 实现内存保护和共享

多级页表结构

为了高效管理页表,现代CPU使用多级页表结构:

  1. 页目录表:由CR3寄存器指向,包含1024个页表项
  2. 页表:每个页表包含1024个页表项,指向物理页
  3. TLB快表:缓存最近使用的页表项,加速地址转换

当CPU访问内存时,MMU(内存管理单元)会:

  1. 首先查询TLB快表
  2. 如果未命中,则查询多级页表
  3. 如果页表项不存在,则触发缺页异常

线程的优势

  1. 创建代价低:创建线程比创建进程开销小得多
  2. 切换速度快
    • 线程切换不改变虚拟内存空间,TLB不需要刷新
    • 只需要保存/恢复少量寄存器内容
  3. 资源占用少:线程共享进程资源,额外开销小
  4. 并行性好:能充分利用多处理器核心
  5. I/O重叠:可以在等待I/O时执行其他计算任务

线程的缺点

  1. 性能损失:计算密集型线程过多可能导致额外调度开销
  2. 健壮性降低:一个线程崩溃可能导致整个进程终止
  3. 编程复杂:需要考虑线程同步和数据竞争问题
  4. 缺乏隔离:线程间缺乏保护机制,容易相互干扰

线程与进程的资源对比

线程共享的资源

  • 代码段(Text Segment)
  • 数据段(Data Segment)
  • 堆空间
  • 文件描述符表
  • 信号处理方式
  • 用户ID和组ID
  • 当前工作目录

线程独有的资源

  • 线程ID
  • 寄存器组(线程上下文)
  • 栈空间
  • errno变量
  • 信号屏蔽字
  • 调度优先级

线程虽然是进程内的执行分支,共享进程的代码、数据、堆等资源,但每个线程仍然需要维护自己的独立执行环境。其中,寄存器组(线程上下文)栈空间是线程独有资源的关键部分,它们确保了线程可以独立执行,不受其他线程干扰。

1. 寄存器组(线程上下文)

(1)为什么线程需要独立的寄存器组?
CPU 执行指令时,依赖寄存器存储当前的计算状态,例如:

  • 程序计数器(PC / EIP / RIP):记录当前执行指令的地址
  • 栈指针(SP / ESP / RSP):指向当前线程的栈顶
  • 通用寄存器(EAX, EBX, ECX, EDX…):存储临时计算结果
  • 状态寄存器(EFLAGS):记录运算状态(如进位、溢出等)

由于 线程是 CPU 调度的基本单位,操作系统在切换线程时,必须保存当前线程的寄存器状态(上下文),并恢复下一个线程的寄存器状态,否则计算会出错。

(2)线程切换时寄存器的保存与恢复
线程切换(上下文切换)的过程:

  1. 保存当前线程的寄存器状态(存入线程的 task_structTCB 结构体)。
  2. 加载目标线程的寄存器状态(从它的 task_struct 恢复)。
  3. CPU 继续执行目标线程

如果多个线程共享同一套寄存器,切换时数据会混乱,因此 每个线程必须有自己的寄存器副本


2. 栈空间

(1)为什么线程需要独立的栈?
栈(Stack)用于存储:

  • 函数调用时的返回地址(调用 call 指令时压栈)
  • 局部变量
  • 函数参数
  • 临时数据

如果多个线程共享同一个栈:

  • 函数调用链会混乱(A 线程调用 func1(),B 线程调用 func2(),栈帧会交错,导致程序崩溃)。
  • 局部变量可能被覆盖(线程 A 的变量可能被线程 B 修改)。

因此,每个线程必须有自己的栈空间,确保函数调用和局部变量的独立性。

(2)线程栈的分配方式

  • 主线程:通常使用进程的默认栈(由操作系统或编译器分配)。
  • 子线程:由 pthread_create() 动态分配栈(默认大小通常为 2~8MB,可调整)。

线程作为轻量级的执行单元,在现代操作系统中扮演着重要角色。理解线程的工作原理、资源分配方式以及与进程的关系,对于编写高效、可靠的并发程序至关重要。合理使用多线程可以显著提升程>序性能,但也需要注意线程安全、同步和资源管理等问题。