039 线程概念

线程概念

1. 什么是线程?它和进程的关系?

1. 粒度:执行的“颗粒大小”

粒度(Granularity) 是个比喻术语,表示一个单位在调度或执行上的“精细程度”。举例说明:

  • 进程 是一个较大单位(粗粒度):拥有独立地址空间、资源。

  • 线程 是进程内部的小单位(细粒度):共享地址空间,调度更轻便。

  • 比喻一下:一个公司(进程)可能有多个部门(线程),每个部门是公司内部的执行单位,共享同一个资源(办公室、资金)。

线程执行进程的代码(粒度细)

  • 多线程可以同时执行同一个进程的多个代码分支,这比进程切换效率高。
  • 一个进程内部多个线程共同完成任务,就像车间里多个工人一起干活。

2. 线程的定义

线程是操作系统调度的最小执行单位。

  • 在用户/开发者角度看:线程是进程中的“执行分支”,多个线程可以并发执行进程中的不同任务。

  • 在内核角度看:内核调度的是线程(Linux 称为“轻量级进程”),不是传统意义上的进程。

  • CPU 视角:只有“执行流”,每个 task_struct(无论是进程还是线程)在 CPU 看来都是“可调度实体”。因此 Linux 把线程干脆叫“轻量级进程”——名字里仍带“进程”,只是资源复用程度不同。

执行流 = CPU 上能被调度运行的最小单位,无论是:主线程执行 main()、子线程处理任务、内核线程处理 I/O、甚至信号处理函数上下文切换,本质上,它们都是“一个在 CPU 上执行的流动单元”。所以我们抽象地称它为 执行流


CPU 只调度执行流(线程):CPU 的调度单位是线程(执行流),不是进程。所以:CPU 只管“谁来执行”,而不是“哪个进程”执行。

3. Linux 中线程的实现方式?

PCB 结构理解(进程是资源实体):进程 = PCB(Process Control Block) + 代码/数据

  • PCB 记录了进程的所有管理信息(PID、状态、内存映射、文件表等)。
  • 线程依附进程执行,不拥有自己的完整 PCB,只在进程的 PCB 中挂靠自己的线程控制块。

Linux 并没有原生的“线程”这个独立结构,而是通过内核的 进程机制(task_struct) 来实现线程,无论是普通进程、还是线程,在内核中都是 task_struct 实例。在 Linux 中,线程就是一种特殊的“进程”,又叫 轻量级进程(Lightweight Process,LWP)。多个线程共享同一个进程的资源(代码段、数据段、堆、文件描述符等),但有自己独立的:

  • 栈空间(私有栈)
  • 寄存器现场(上下文)
  • 线程 ID(TID)

[!NOTE]

那这样做的好处(优雅性)是什么?

这正是 Linux/OS 设计哲学的体现:

  • 统一的数据结构,简化调度系统。
  • 灵活的共享机制。
  • 复用代码(内核的数据结构),降低 bug 的可能性和维护难度。

Windows 内核线程是“真正意义上的线程”:Windows 内核中有 线程控制块,叫做 TCB(struct TCB,Thread Control Block)。它是 Windows 原生线程结构,用于记录线程上下文、栈地址、状态等,是 Windows 的调度单位。Windows 中,进程是资源容器,线程是独立的执行单位,系统明确区分这两个概念。

4. 为什么线程要在进程“内部”执行?

  • 原因:线程不拥有自己的资源,必须依附进程的资源(如地址空间)才能执行。 任何执行流想跑起来都必须:有代码(指令)、有栈、有页表(地址空间)(加粗是必要条件!),全部条件还需要 task_struct(调度实体)、上下文。
  • 所以:“地址空间是进程的资源窗口”,线程就是进程内部的执行分支。线程“在进程地址空间内跑”不是可选,而是必选项;离了地址空间,CPU 连下一条指令在哪都不知道。

image-20250720155626624

5. 进程 vs 线程

【os 浅尝】话说进程和线程~ | B 站
进程和线程的区别 | B 站
【操作系统】进程和线程的区别 | B 站
线程、进程和应用程序的关系和原理(干货教程) | B 站

对比点 进程 线程
是否独立 独立,拥有自己的地址空间、页表 依附于进程,共享进程地址空间
是否拥有资源 是,拥有文件描述符、内存等 否,只拥有少量运行所需资源
调度粒度 粗(整个进程调度) 细(线程调度)
创建/销毁开销 大(复制页表、资源) 小(切换栈、寄存器)
通信效率 低(需进程间通信 IPC) 高(共享内存)
健壮性 进程崩溃不影响其他进程 线程崩溃导致整个进程崩溃

image-20250720160747631

2. 页表

页式存储管理讲解 | B 站

「Coding Master」第 30 话 这个内存分页,就挺难的 | B 站

CPU 眼里的:二级页表 | MMU | 虚拟内存 | B 站

一步一图带你构建 Linux 页表体系 —— 详解虚拟内存如何与物理内存进行映射 | 博客园

1. 什么是页表(Page Table)?

1. 本质

页表是操作系统用来 管理虚拟地址与物理地址映射关系 的数据结构。每个进程都有自己的 虚拟地址空间 ,而页表就是这个地址空间的“地图”,告诉 CPU 如何将一个虚拟地址转换为物理地址。简单说:我们访问的是虚拟地址,系统通过页表找出对应的物理地址。

2. 页表的核心单位

名称 含义 单位 特点 举例
页面(Page) 虚拟地址空间被划分为固定大小的块 通常为 4KB 虚拟地址的基本单位 每个页面对应一个物理页框
页框 / 页帧(Page Frame) 物理内存被划分为固定大小的块 通常为 4KB 存放页面数据的物理单元 物理内存中的 4KB 单元
页目录项(PDE) 一级页表项,指向页表 4 字节(32 位) 页目录有 1024 项 PDE [i] → 页表起始地址
页表项(PTE) 二级页表项,指向页框 4 字节(32 位) PTE [i] → 页框起始地址 用于虚拟地址到物理地址映射
页内偏移(Offset) 页内地址偏移量 12 位(0~4095) 用于在页框中定位具体字节 偏移量 + 页框地址 = 物理地址

image-20250720151706339

image-20250720152800793

3. 为什么需要页表?

页表是实现虚拟内存机制的基础,二级页表是为了节省内存并提高效率,多级页表则是为了支持更大的地址空间和更灵活的管理方式。

为什么需要二级页表?

单级页表的问题(以 32 位系统为例):32 位系统地址空间为 4GB(2^32),页大小为 4KB(2^12),总共需要 2^20 = 1,048,576 个页表项,每个页表项 4 字节,总大小为 4MB。所以:页表大小 = 2²⁰ × 4B = 4MB,每个进程都要维护一张 4MB 的页表(即使它只用了一点虚拟内存),这对于上了年代的机器来说是灾难性设计!

就是因为占内存大、浪费严重、不支持“按需分配”所以诞生了二级页表,二级页表是对“空间换时间”的优化倒转 —— 用时间(多查一次)换空间(只分配用到的),再到后来的多级页表:节省空间、支持按需映射、提升效率。

4. 32 位系统中二级页表的工作原理结构图

image-20250720153638765

1. 分解说明
  • 虚拟地址 = 32 位: 虚拟地址由 3 部分组成:

    1
    2
    [10位 页目录索引] [10位 页表索引] [12位 页内偏移]
    (2^10 条) (2^10 条) (4KB 页面)
  • 页大小 = 4KB = 2¹²: 最后 12 位表示页内偏移(Offset)

  • 页表项大小 = 4 字节(32bit): 一个页表(4KB)能容纳:4KB ÷ 4B = 1024 条项,刚好对应 2¹⁰。

2. x86 32 = 10+10+12 的意义

虚拟地址 32 位 = 10(页目录)+ 10(页表)+ 12(偏移)

位数 说明
页目录 10 一级表,存放 页表的指针,选择哪个页表(共 1024 个)
页表 10 二级表,存放 页 → 物理帧 映射,选择哪个页表项
偏移 12 不变,表示页中具体偏移地址(4KB 页大小)

image-20250720154605759

3. 再谈线程

1. 线程的优点(相对进程)

编号 优点描述 解释说明或场景举例
1 创建开销小 新线程共享父进程资源,不需要重新分配地址空间等
2 上下文切换代价小 线程切换只需切换少量寄存器,进程切换需切页表等
3 占用资源少 共享代码段、数据段、堆等,减少内存资源开销
4 支持并发执行 多线程可利用多核 CPU,提高执行效率
5 提升 IO 密集程序性能 下载、读写磁盘等可异步等待,主线程可继续执行
6 提升计算密集程序性能(多核) 可将任务拆分成多个子任务,多个核并行执行
7 IO 操作可并发等待,提高吞吐 多线程各自等待不同 IO 资源,实现重叠与效率提升

2. 线程的缺点

编号 缺点描述 举例或解释
1 同步/调度带来性能损失 多个计算密集型线程争抢 CPU,带来线程调度/同步开销
2 健壮性降低 不小心共享了不该共享的变量,或同步出错易出 bug
3 缺乏访问隔离保护 所有线程共享进程资源,某线程异常可能影响整个进程
4 编程难度高 多线程程序调试困难,如死锁、竞态条件难定位

3. 线程异常处理风险(容易忽略)

问题 描述说明
崩溃传染性强 一个线程崩溃(如除 0、野指针),触发信号机制,整个进程被终止
同步问题 锁没加好、条件变量判断失误,可能引发死锁、数据污染等不可预测问题
资源泄漏 某线程提前崩溃未释放资源,整个进程泄漏,所有线程随之退出

4. 线程适用场景(牢记)

场景 类型 描述示例
CPU 密集型 多核计算 视频渲染、图片处理、机器学习模型训练、加密解密
IO 密集型 高并发 IO 网络爬虫、数据库操作、磁盘文件下载、日志写入
用户体验优化 前后台并行处理 一边写代码一边下载依赖,一边加载图片一边展示 UI

5. 记忆口诀

  • 线程优点快轻小,共享资源效率高;
  • 线程缺点易崩溃,调试困难需谨慎;
  • CPU 并行靠分工,IO 并发靠等待;
  • 善用线程提性能,滥用线程埋雷坑。

线程相较于进程最大的优势是开销小、切换快、共享资源,适合高并发或高性能场景。比如在多核处理器中跑 CPU 密集任务,或者处理大量 IO 等待时都能有效提升效率。但线程也存在缺乏隔离、同步复杂、异常传播等风险,因此使用时需要严格管理共享资源和同步机制。