目录

一、线程理解

1.1 感性理解线程

1.2 理解 Linux 下的线程

1.2.1 task_struct:Linux 的调度实体

1.2.2 进程的本质

1.2.3 线程的本质

1.2.4 Linux 为什么称线程为 “轻量级进程”

1.2.5 设计思想(Linux vs Windows)

Windows 设计方式:

Linux 设计方式:

1.2.6 小结

1.3 线程概念的补充与理解

 1.3.1 进程与线程的数量关系

1.3.2 线程的运行本质

1.4 线程与进程的对比

1.4.1 进程和线程的区别

1.4.2 线程的优点

1.4.3 线程的缺点

1.4.4 线程共享数据与“私有”数据


一、线程理解

1.1 感性理解线程

从操作系统教材的角度来看:

进程:由内核管理的资源分配单位,通常由代码段、数据段以及内核为其维护的数据结构组成(内核数据结构 + 代码和数据)。

线程:进程内部的一个执行流,是程序执行的基本单位。

从内核与资源管理的角度来看:

进程:资源分配的基本单位

线程:CPU 调度的基本单位

需要注意的是,教材中的定义更多是一种抽象模型,用于帮助理解操作系统的设计思想,而不是某一种固定的实现方式。

事实上,不同操作系统对“进程”和“线程”的实现差异较大,但它们背后的设计思想是一致的。

因此,在学习操作系统时,我们不能只停留在抽象概念上,而应该进一步结合具体实现来理解它们的本质。

在本章中,我们将从 Linux 操作系统的实现角度 出发,先分析进程与线程的具体实现方式,再回到概念层面进行抽象总结。

1.2 理解 Linux 下的线程

在 Linux 操作系统中,线程并不是一个与进程完全独立的新概念,而是一种 轻量化进程

在 Linux 内核中,调度的基本单位不是 “进程” 或 “线程”,而是一个统一的结构:task_struct

1.2.1 task_struct:Linux 的调度实体

在 Linux 内核中,无论是进程还是线程,都统一由 task_struct 表示。

因此可以理解为:

CPU 调度的对象:task_struct

细节问题:CPU调度的对象为什么是 task_struct ,而不是线程?

由于操作系统的标准,Linux 确实要有线程的概念,但在内核中不一定要具体实现,而 Linux 采取的实现方案就是用轻量化进程(task_struct)来模拟实现线程的,为了让用户使用不去关注 Linux 独有的轻量化进程相关的概念,Linux 通过线程库封装了底层,让用户只需关注线程,无需关注轻量化进程。

1.2.2 进程的本质

如图所示,一个进程并不是单一的结构,而是一个资源与执行实体的组合

  • task_struct(线程)
  • mm_struct(虚拟地址空间)
  • 文件描述符表
  • 信号处理结构等

因此从操作系统角度来看:

进程 = 资源集合 + 一个或多个 task_struct

1.2.3 线程的本质

如图所示,在 Linux 中线程同样由 task_struct 表示。

但与进程不同的是:

同一进程中的多个线程,共享同一个 mm_struct(虚拟地址空间)

因此可以得到核心结论:

Linux 中线程 = 共享资源的 task_struct 的执行流

1.2.4 Linux 为什么称线程为 “轻量级进程”

所谓“轻量”,本质体现在资源共享程度:

线程与进程在 Linux 中的区别,不是结构不同,而是共享资源的范围不同

  • 进程:拥有独立 mm_struct(地址空间隔离)

一个进程拥有一个虚拟地址空间,保证了进程之间的独立性

  • 线程:共享 mm_struct(地址空间共享)

一个进程的多个线程之间可以共享同一份虚拟地址空间,保证了线程之间的共享性

1.2.5 设计思想(Linux vs Windows)

Windows 设计方式:

Windows 在进程内部显式维护线程结构(TCB),进程与线程在内核中是相对分离的模型。

  • 进程:PCB
  • 线程:TCB

属于“进程 + 线程分离模型”。


Linux 设计方式:

Linux 没有单独设计线程结构,而是:

复用 task_struct,用轻量化进程模拟线程

这样设计的原因是:

  • 线程与进程在调度层高度相似
  • 上下文切换机制几乎一致
  • 复用结构可以减少内核复杂度
  • 提高代码一致性与稳定性

1.2.6 小结

  • CPU 在 Linux 中的调度基本单位是 task_struct
  • 进程与线程的本质区别在于资源共享范围
  • 线程是共享 mm_struct 的 task_struct
  • 虚拟地址空间是资源隔离与共享的核心边界

1.3 线程概念的补充与理解

在 Linux 中,我们已经知道:

  • CPU 调度单位是 task_struct
  • 进程与线程都由 task_struct 表示
  • 二者的区别在于资源是否共享

因此可以得到一个更本质的结论:

线程是进程内部的一个执行流,是 CPU 调度的基本单位。


 1.3.1 进程与线程的数量关系

一个进程至少包含一个线程,该线程称为主线程

  • 只有一个线程的进程称为单线程进程
  • 包含多个线程的进程称为多线程进程

1.3.2 线程的运行本质

线程的运行,本质是在共享的虚拟地址空间中独立执行

在 Linux 中:

  • 一个进程的所有线程共享同一个 mm_struct
  • 共享同一套虚拟地址空间
  • 通过页表映射访问物理内存

所以线程执行时:

访问的资源本质上都是通过虚拟地址空间进行访问的

1.4 线程与进程的对比

1.4.1 进程和线程的区别

对比维度 进程 线程(同进程内)
内核角度 资源分配最小单位 CPU 调度最小单位
地址空间 每个进程独立虚拟地址空间,相互隔离 共享所属进程的虚拟地址空间
上下文切换开销 开销大,需要修改 CR3、冲刷 TLB、Cache 大量失效 开销小,仅保存恢复少量线程私有寄存器,无需切换页表
数据共享 进程间数据共享困难,需要 IPC(管道、共享内存、socket 等)通信,开销高 直接读写全局变量、堆数据,共享简单高效
生命周期与依赖 相互独立,一个进程崩溃不会影响其他进程 依赖所属进程,一个线程捕获致命异常会导致整个进程崩溃
开销 创建、销毁开销大(需要分配地址空间、页表等资源) 创建、销毁开销小,仅分配线程私有栈
安全性 进程间隔离性强,一个进程出错不会干扰其他进程 线程无隔离,共享资源极易出现线程安全问题

1.4.2 线程的优点

创建一个新线程比创建一个新进程成本更低,线程占用的资源比进程少。

原因:创建一个新线程,OS 只需要为其分配 task_struct,不需要为其分配虚拟地址空间和页表。

线程切换与进程切换相比,线程切换更低。

注:这里的线程切换,是指同一个进程的多线程进行切换。

第一点原因:线程切换无需切换虚拟地址空间,进程切换必须切换虚拟地址空间(基础原因)

底层:同一进程下所有线程共享一套虚拟地址空间与页表,CPU 中 CR3 寄存器存放当前页目录起始物理地址,线程切换不需要修改 CR3 寄存器;而进程拥有独立虚拟地址空间,进程切换必须重新赋值 CR3 完成页表切换。

第二点原因:进程上下文切换对 CPU 缓存机制的破坏程度远高于线程切换(核心原因)

底层:CPU 内置 Cache 高速缓存与 TLB 地址转换缓存:

  • Cache 缓存 CPU 近期访问过的指令、数据以及对应地址标签,依托局部性原理提升访问速度;同进程线程共用虚拟地址空间,线程切换不会造成 Cache 大规模失效;进程切换更换虚拟地址空间,会导致 L1、L2 高速缓存绝大部分失效,只有少数共享内存区域、物理寻址的缓存可以复用。
  • TLB 用于缓存虚拟地址到物理地址的映射,避免频繁遍历多级页表;线程切换不修改 CR3,TLB 内的地址映射可以继续复用;进程切换修改 CR3 后,TLB 中的内容会被硬件大量清空,需要重新缓存新的地址映射关系。 

在 IO 等待场景下,部分线程阻塞等待 IO 数据,CPU 会调度同进程内其他线程继续执行任务,提升整体任务执行效率。

不选择多进程的原因:进程间地址空间相互独立,进程间共享数据需要借助进程间通信方式,资源共享开销高;同时进程上下文切换代价远大于线程切换。

在计算密集型场景下,可将大任务拆分,利用多核 CPU 通过多线程并行执行,缩短任务总耗时。不选用多进程的原因和 IO 密集型场景一致:进程资源共享成本高、上下文切换开销大。

补充说明:

  1. IO 密集型场景依靠并发即可获得性能提升:单核 CPU 下多线程交替执行,IO 阻塞时释放 CPU 给其他线程,避免 CPU 闲置;
  2. 计算密集型场景只有在多核 CPU 实现并行执行时才能缩短运行时间;若仅单核并发执行,多线程不仅无法提速,还会因频繁的线程切换产生额外开销,性能略有下降。

1.4.3 线程的缺点

性能损失:在一个很少 IO 且计算密集型的场景下,如果同进程创建的线程数量多于可用的 CPU 核心数,那么多线程不仅无法提速,还会因为频繁的线程切换产生额外开销,造成性能损失。

缺乏访问控制:在同一进程的多线程中,线程之间共享进程的虚拟地址空间,能看到同一份资源,如果不对要修改的资源进行保护,就会导致线程之间对同一份资源读写时产生竞争,引发数据错乱,结果异常等线程安全问题。

异常代价大:在同一进程的多线程中,如果一个线程触发异常,操作系统会先整个进程发送信号,导致争个进程内所有线程全部退出。

上述缺陷大多可以通过合理的编码设计规避风险:例如使用线程池控制线程数量减少切换开销、采用互斥同步机制保证共享资源访问安全、注册异常捕获与信号处理函数避免进程意外崩溃。

1.4.4 线程共享数据与“私有”数据

线程之间共享进程的虚拟地址空间,那么虚拟地址空间上的代码区、数据区、堆区等都是共享的,

除此之外,线程之间还共享:

例如

文件描述符表

信号的处理方式

当前工作目录

用户id和组id

线程之间的“私有数据”

例如:

线程ID

一组寄存器,线程的上下文数据

errno

信号屏蔽字

调度优先级

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐