Skip to content

线程与进程

简介

本文介绍进程与线程的基本概念以及它们之间的关系和区别,不同操作系统对线程的实现方式以及 Java 线程模型的特点,上下文切换、并发与并行。

进程

存储在硬盘的静态文件(源代码),通过编译后生成二进制可执行文件,当运行这个可执行文件后,它会被加载到内存中,接着 CPU 会执行程序中的每一条指令,即运行中的程序,就被称为进程(Process)。

硬盘可执行文件内存CPU源代码文件运行中的程序1.编译:2.加载:3.执行:

线程

线程(Thread)是进程内的执行单元,是 CPU 调度的基本单位。每个进程至少包含一个线程(主线程),也可以创建多个线程来执行不同的任务。

进程与线程区别

特性进程线程
定义运行中的程序的实例,是系统进行资源分配和调度的基本单位进程中的执行单元,是 CPU 调度的基本单位
资源占用拥有独立的内存空间、文件描述符等系统资源共享所属进程的内存空间和资源
通信方式进程间通信(IPC):管道、消息队列、共享内存、信号量、套接字等线程间可直接通过共享变量通信
切换开销切换开销大,涉及到虚拟内存、页表等切换切换开销小,只需保存和恢复少量寄存器内容
创建销毁开销创建和销毁开销大创建和销毁开销小
并发性多进程并发多线程并发
健壮性一个进程崩溃不会影响其他进程一个线程崩溃会导致整个进程崩溃
操作系统进程1进程2线程1-1线程1-2线程2-1

线程实现

不同操作系统对线程的实现方式有所不同,主要可以分为三种实现模型:用户级线程模型、内核级线程模型和混合线程模型。

1. 用户级线程模型(User-Level Threads)

用户级线程是完全建立在用户空间的线程库上,系统内核不能感知用户级线程的存在。

特点:

  1. 线程管理在用户空间完成,不需要内核支持

  2. 线程切换无需切换到内核态,开销小

  3. 可以实现特定的调度算法

  4. 可以支持大规模的线程数量

缺点:

  1. 一个阻塞型系统调用会导致整个进程阻塞

  2. 无法利用多处理器的并行能力

操作系统进程用户线程1用户线程2用户线程3线程库感知进程管理管理管理

2. 内核级线程模型(Kernel-Level Threads)

内核级线程(KLT)是由操作系统内核支持的线程,系统内核负责线程的创建、调度和管理。

特点:

  1. 内核直接支持和管理线程

  2. 一个线程阻塞不会导致整个进程阻塞

  3. 可以充分利用多处理器的并行能力

缺点:

  1. 线程创建和切换的开销较大

  2. 线程数量受系统资源限制

操作系统内核进程内核线程1内核线程2内核线程3用户线程1用户线程2用户线程31:1映射1:1映射1:1映射

3. 混合线程模型(Hybrid Threading Model)

混合模型结合了用户级线程和内核级线程的优点,实现了多对多的线程映射关系。

特点:

  1. 结合了用户级和内核级线程的优点

  2. 可以同时支持大量线程和多处理器并行

  3. 灵活性更高

实现方式:

  1. 多对一模型(M:1):多个用户线程映射到一个内核线程,即上述的用户级线程模型

  2. 一对一模型(1:1):每个用户线程映射到一个内核线程,即上述的内核级线程模型

  3. 多对多模型(M:N):M 个用户线程映射到 N 个内核线程,综合了前两种模型的优点

操作系统内核进程内核线程1内核线程2用户线程1用户线程2用户线程3用户线程4线程库管理管理管理管理调度执行调度执行调度执行调度执行

不同编程语言对线程的实现采用了不同的模型:

语言/平台线程模型特点
Java (传统线程)1:1 模型每个 Java 线程映射到一个操作系统线程,重量级
Java (Virtual Threads, Java 21+)M:N 模型大量虚拟线程映射到少量操作系统线程,轻量级
Go (goroutine)M:N 模型轻量级协程,可以创建数十万个
Python (CPython)用户级线程受全局解释器锁 (GIL) 限制,无法真正并行
Node.js事件循环 + 线程池主线程处理事件循环,线程池处理阻塞操作
C/C++ (POSIX threads)1:1 模型直接映射到操作系统线程

Java 线程模型

Java 采用基于操作系统原生线程的一对一线程模型,具有以下特点:

  1. Java 线程与操作系统线程直接映射:每个 Java 线程对应一个操作系统线程

  2. 线程调度依赖操作系统:线程的调度由操作系统完成,JVM 不负责线程调度

  3. 线程优先级映射:Java 线程的优先级会映射到操作系统线程的优先级,但具体映射关系依赖于操作系统

  4. 轻量级进程(LWP):在某些系统上,Java 线程实际上是基于轻量级进程实现的

Java 虚拟机Java 线程 1Java 线程 2操作系统线程 1操作系统线程 2

注意

与 Go 语言的协程(goroutine)不同,Java 的线程是重量级资源,创建和销毁成本较高。Java 21 引入的虚拟线程是对此问题的解决方案。

上下文切换

上下文切换(Context Switch)是指 CPU 从一个线程或进程切换到另一个线程或进程的过程。在这个过程中,需要保存当前线程的状态(上下文),并加载另一个线程的状态。

什么是线程上下文

线程上下文包括:

  1. 寄存器状态:包括程序计数器(PC)、堆栈指针等

  2. 线程特有数据:如在 Java 中的 ThreadLocal 变量

  3. 内存映射信息:线程当前访问的内存区域

  4. 资源句柄:线程打开的文件描述符等

  5. 调度信息:优先级、状态等

上下文切换的过程

一次完整的上下文切换主要包括以下步骤:

  1. 保存当前线程上下文:将当前线程的寄存器值、程序计数器等保存到内存中

  2. 选择下一个要运行的线程:调度器根据调度算法选择下一个线程

  3. 恢复下一个线程的上下文:从内存中加载目标线程的寄存器值、程序计数器等

  4. 切换到新线程执行:CPU 开始执行新线程的指令

线程 A (运行中)保存上下文调度器选择新线程加载上下文线程 B (运行中)1.保存状态2.进入调度3.选择线程B4.恢复状态

并发与并行

并发和并行是多线程编程中两个核心概念,二者有本质区别,理解这些差异有助于设计高效的多线程应用。

并发(Concurrency)

并发是指在同一时间段内,多个任务交替执行。从宏观上看,这些任务似乎是同时进行的,但从微观上看,在单核处理器上,任意时刻只有一个任务在执行。

特点

  • 多个任务交替执行,轮流使用 CPU

  • 适用于 I/O 密集型任务

  • 通过时间片轮转实现

  • 目标是提高资源利用率和响应性

并发单 CPU 核心任务 1任务 2任务 1任务 2任务 1时间轴

并行(Parallelism)

并行是指在同一时刻,多个任务真正同时执行。这要求系统具有多核处理器或多处理器架构,每个核心同时处理不同的任务。

特点

  • 多个任务同时执行

  • 需要多核/多处理器环境

  • 适用于计算密集型任务

  • 目标是提高吞吐量和计算速度

并行CPU 核心 1CPU 核心 2CPU 核心 3CPU 核心 4任务 1任务 2任务 3任务 4时间轴

并发与并行的比较

下表详细比较了并发和并行的主要特性:

特性并发并行
定义多个任务交替执行多个任务同时执行
执行方式时间片轮转真正的同时执行
硬件要求单核即可需要多核/多处理器
资源竞争存在竞争,需要同步各自独立执行,潜在竞争少
复杂度通常更复杂(需处理竞态条件)相对简单(数据隔离时)
适用场景I/O 密集型、交互型应用计算密集型、数据处理
设计重点资源共享和同步任务分解和负载均衡
可扩展性受单核性能限制可随核心数增加而线性扩展

参考资料