【多线程】多线程的底层实现
【多线程】多线程的底层实现?
本文来自于我关于多线程系列文章。欢迎阅读、点评与交流
1.【多线程】互斥锁(Mutex)是什么?
2.【多线程】临界区(Critical Section)是什么?
3.【多线程】计算机领域中的各种锁
4.【多线程】信号量(Semaphore)是什么?
5.【多线程】信号量(Semaphore)常见的应用场景
6.【多线程】条件变量(Condition Variable)是什么?
7.【多线程】监视器(Monitor)是什么?
8.【多线程】什么是原子操作(Atomic Operation)?
9.【多线程】竞态条件(race condition)是什么?
10.【多线程】无锁数据结构(Lock-Free Data Structures)是什么?
11.【多线程】线程休眠(Thread Sleep)的底层实现
12.【多线程】多线程的底层实现
13.【多线程】读写锁(Read-Write Lock)是什么?
多线程的底层实现是一个涉及硬件、操作系统和编程语言运行时的复杂协同过程。
我们可以从两个层面来理解:
- 软件层面:操作系统如何管理线程。
- 硬件层面:CPU 如何执行线程。
一、软件层面:操作系统的线程模型
操作系统是线程管理的核心,它通过内核来实现。主要有三种模型:
1. 用户级线程
- 概念:线程的创建、调度、同步和管理完全在用户空间的运行时库(如线程库)中完成,内核对此一无所知。内核的调度单位仍然是进程。
- 工作原理:
- 一个进程内部有一个运行时系统,负责在多个用户级线程之间切换。
- 当某个用户级线程执行系统调用(如I/O操作)导致阻塞时,由于内核只知道这个进程,所以它会将整个进程阻塞,导致该进程内的所有用户级线程都无法执行。
- 优点:
- 极快的线程切换:因为切换不需要陷入内核态,没有上下文切换的开销。
- 可定制调度算法:每个进程可以为自己的线程定制调度策略。
- 缺点:
- “一损俱损”:任何一个线程的阻塞会导致整个进程阻塞,无法真正利用多核CPU。这就是所谓的“非真正并行”。
2. 内核级线程
- 概念:线程的管理工作直接由操作系统内核完成。内核负责线程的调度、同步和资源分配。
- 工作原理:
- 内核为每个线程维护一个内核级的数据结构(如Windows的ETHREAD,Linux的task_struct)。
- 线程的创建、销毁和切换都需要通过系统调用进入内核,由内核来完成。
- 优点:
- 真正并行:内核可以将不同线程分配到不同的CPU核心上同时执行。
- 健壮性:一个线程的阻塞不会影响同一进程内的其他线程。
- 缺点:
- 线程切换开销大:每次切换都需要在用户态和内核态之间来回切换,成本较高。
3. 混合模型(现代操作系统的主流)
- 概念:结合了上述两种模型的优点。用户级线程与内核级线程存在一种映射关系。
- 工作原理:
- 用户创建和管理大量的用户级线程。
- 这些用户级线程被多路复用到数量较少的内核级线程(通常称为“轻量级进程”或LWP)上。
- 内核只看到LWP,并负责调度它们到CPU上执行。
- 用户级的线程库负责将用户级线程分配到可用的LWP上。
- 优点:
- 创建和切换大量用户级线程很快。
- 通过合理数量的LWP,可以实现真正的多核并行。
- 编程灵活,可以调整用户线程与LWP的比例。
- 例子:Java的线程模型在Linux上就是通过这种方式实现的(用户线程映射到内核线程)。
二、硬件层面:CPU 如何支持多线程
操作系统最终需要硬件的支持才能实现并行。
1. 多核处理器
- 这是最直观的支持。一个物理CPU芯片上集成了多个独立的计算核心(Core)。每个核心都有自己的ALU、寄存器组和L1/L2缓存。
- 实现方式:操作系统可以将不同的线程(内核级线程)安排到不同的物理核心上真正并行执行。
2. 超线程技术
- 概念:一种“同时多线程”技术,旨在提高单个物理核心的利用率。
- 工作原理:
- 一个物理核心内部有多套架构状态(如寄存器组、程序计数器等),但只有一套执行单元(ALU、FPU等)。
- 从操作系统看来,一个支持超线程的物理核心就像两个“逻辑核心”。
- 当其中一个逻辑核心在等待数据(比如缓存未命中)而停滞时,执行单元可以立刻去执行另一个逻辑核心的指令。
- 本质:通过让两个线程交替使用CPU的执行资源,来模拟出两个核心的效果。它是一种并发,而不是真正的并行。但它确实能有效提升CPU吞吐量。
三、核心机制:线程上下文切换
这是多线程实现的基石。
-
触发时机:
- 线程的时间片用完。
- 线程主动让出CPU(如等待I/O、锁、调用
yield()
)。 - 被更高优先级的线程抢占。
-
切换过程:
- 保存上下文:将当前正在运行的线程的CPU状态(包括程序计数器、寄存器内容、栈指针等)保存到其线程控制块(TCB)中。
- 调度:操作系统调度器从就绪队列中选择下一个要运行的线程。
- 恢复上下文:将选中线程的TCB中的状态加载到CPU的寄存器和程序计数器中。
- 切换地址空间(可选):如果切换的是属于不同进程的线程,还需要切换页表(内存映射),这会清空TLB,开销更大。同进程内的线程切换则不需要这一步。
- 恢复执行:CPU从新的程序计数器位置开始执行,即开始执行新的线程。
总结
多线程的底层实现是一个分层协作的复杂过程:
- 编程语言(如Java, C++)提供线程库(如
java.lang.Thread
,std::thread
),为开发者提供易用的接口。 - 操作系统内核是真正的管理者,它通过内核级线程和混合模型来管理和调度线程,实现并发与并行。其核心机制是上下文切换。
- 硬件(CPU) 提供物理基础,通过多核实现真正的并行,通过超线程等技术提高单个核心的利用率和系统吞吐量。
简单流程图:
你的程序创建线程
-> 编程语言运行时库调用操作系统API
-> 操作系统内核创建/管理内核线程
-> 操作系统调度器将内核线程分配到CPU的物理/逻辑核心上执行
-> CPU核心通过上下文切换在多个线程间快速轮转,造成“同时运行”的假象。