1. 为何需要线程

我们知道,在没有线程的早期操作系统中,进程既是资源分配单位,也是 CPU 调度的单位。讲大白话就是:进程不仅要作为一个容器把程序的代码、堆内存、打开的文件等资源包裹(拥有)起来,还要亲自下场排队等 CPU 执行。但是这样开销太大了。因为每次 CPU 调度切换进程时,内核不仅要保存当前的执行状态,还必须切换整个内存地址空间(即更换页表)。这种操作会导致 CPU 内部的寻址缓存(TLB)全部失效,引发后续大量的内存访问延迟,导致程序并发执行时性能大幅下降。

为了解决这个痛点,引入线程机制,本质上就是为了将‘资源拥有者’和‘执行调度实体’彻底剥离开来。进程退化为纯粹的资源分配容器(只负责共享代码段、数据段、堆空间等),而把排队等 CPU 执行的脏活累活交给了线程。线程由此成为了轻量级的 CPU 调度基本单位,因为同一个进程下的线程切换不需要换页表,极大节省了并发调度的时空开销。

2. 解剖线程

对于操作系统来说,一个任务就是一个进程(Process),比如打开一个记事本,那么就是启动了一个记事本的进程,其内部会裂变出多个并行的子任务(线程),各自负责不同的工作:

  • 线程 A(UI 渲染线程):死循环监听鼠标键盘,你敲一个字,它往屏幕上画一个字。
  • 线程 B(后台保存线程):每隔几秒,默默把内存里的文本往硬盘里写。
  • 线程 C(拼写检查线程):在后台静默比对词库,发现错词就画红波浪线。

2.1 它们共享什么(进程级资源):

这三个线程同属一个进程,它们在底层共享了同一套虚拟内存地址空间和文件描述符表。

  • 代码段:记事本的二进制指令。A、B、C 线程跑的底层逻辑都在同一块只读内存里。
  • 数据段 / 堆空间:这是最核心的交互区。比如你在记事本里打下的 10000 个字(文本 Buffer 存放在堆中)。线程 A 要读它来刷新屏幕,线程 B 要读它来写入磁盘,线程 C 要读它来查错。大家看的是同一段内存,这也是多线程编程必须加锁(Mutex/Spinlock)的原因,否则 A 刚删了一段文字,B 刚好去读,程序直接崩溃。
  • 打开的文件描述符 (fd):记事本打开的 .txt 文件句柄。线程 B 存盘时直接复用这个 fd,不需要重新向系统申请打开文件。

2.2 它们私有什么?(线程级资源)

虽然大家在同一个车间(进程)干活,但每个线程必须有自己独立的“脑子”和“工位”,也就是独立的执行上下文。

  • 寄存器上下文 (Context):保存线程被挂起时 CPU 寄存器的状态。最核心的是程序计数器(PC,记录代码执行到了哪一行)和栈指针(SP,指向该线程私有的调用栈栈顶)。
  • 独立的函数调用栈 (Stack):各个线程执行的函数不同,产生的局部变量也不同。
  • 线程局部存储 (TLS):用于存放“线程独享的全局变量”。比如底层的错误码 errno,如果 B 线程写磁盘失败产生了错误码,它只应该存在 B 的 TLS 中,不能影响正在健康运行的 A 线程。

2.3 示例

在这里插入图片描述

3. 多线程模型

线程通常分为用户级线程(ULT,User-Level Thread)和内核级线程(KLT,Kernel-Level Thread)。

其中,用户级线程由线程库在用户空间管理,操作系统无法直接感知,只有内核级线程才能被os调度器管理调度在cpu执行,所以程序员/应用程序创建的用户级线程无法被操作系统直接调度,最终必须通过与内核级线程建立映射关系,才能获得 CPU 执行机会,其经典的映射模型如下:

  1. 多对一(M:1):多个用户级线程映射到同一个内核级线程。
    在这里插入图片描述
    优点是实现简单、线程切换开销较小;缺点是内核只能感知一个执行实体,当该线程发生阻塞系统调用时,整个进程中的所有用户线程都会被阻塞,且无法利用多核处理器实现真正并行。
  2. 一对一 (1:1):每个用户级线程对应一个内核级线程。
    在这里插入图片描述
    该模型是现代 Linux、Windows 等主流操作系统采用的线程模型。在 Linux 中,线程通常以轻量级进程(LWP,Light Weight Process)的形式存在,底层通过 clone() 系统调用创建。该模型能够充分利用多核 CPU,实现真正的并行执行,但线程创建和管理开销相对较高
  3. 多对多 (M:N):多个用户级线程映射到多个内核级线程。
    在这里插入图片描述
    该模型结合了用户态线程调度和内核态线程调度的优点,既能够提高并发能力,又能够减少线程管理开销。但由于实现复杂,目前已较少在主流操作系统中使用。

4. 总结

  1. 线程和进程的身份区别:
  • 以前进程既是资源的基本分配单位也是调度的基本单位,但是为了降低切换成本,通信成本等引出线程作为调度的基本单位,进程则做资源分配的基本单位
  1. 在进程中运行的线程:
  • 共享:代码段、数据段(全局变量)、堆空间、打开的文件描述符等进程资源
  • 私有:线程控制块(TCB)、寄存器上下文、线程局部存储(TLS)以及独立的函数调用栈。
  1. 映射模型:
  • 多对一(M:1):多个用户级线程映射到同一个内核级线程。
  • 一对一 (1:1):每个用户级线程对应一个内核级线程。
  • 多对多 (M:N):多个用户级线程映射到多个内核级线程。
Logo

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

更多推荐