再谈:线程和进程
大家好,本文我想来谈谈进程和线程的区别,即 process 和 thread 的区别。
这是一个老生常谈的话题,几乎每个程序员在面试或考试中都被问过。但我刚开始学习这些概念、在网上查资料时,发现很多解释不够细致,或者没有说到关键点。所以我想分享自己的理解,并尽量讲清楚。
1. 线程
1.1 什么是线程?
先从线程说起。线程是什么?顾名思义,线程像一条线,指的是一段按顺序执行的程序指令。比如 a+b,它是线性的逻辑:先输入 a,再输入 b,把 a 与 b 相加,最后输出结果。在汇编语言中也很清晰地体现出这种顺序执行,这就是我们所说的一条“线”的执行。
1.2 操作系统中的线程切换
线程指的是一段线性执行的程序,这是线程的逻辑定义。
从 CPU 或操作系统的角度看,操作系统本身也是运行在 CPU 上的程序。假设我们有一颗非常朴素的 CPU,它按照取指、译码、执行、读写内存、写回寄存器的顺序工作,每次执行一条指令,按程序员给定的指令序列顺序运行。
当操作系统启动时,它有一个主线程,也就是内核线程,经历初始化并进入调度器循环。需要注意的是,从启动到初始化再到进入调度器,这个过程是线性的,就像我们写的普通程序一样一行一行执行,我们可以称之为内核线程。进入调度器循环后,就出现了第二类线程——用户进程的线程。这里我说“进程”可能有歧义,我指的是用户编写的应用程序的线程。
用户编写的应用程序(例如 a+b)会被编译成一系列指令,这些指令是一段连续逻辑,与内核逻辑无关。所以用户线程与内核线程是两个不同的逻辑实体,也就是两个不同的线程。
在调度器循环中,内核线程会切换到用户线程。在切换过程中,系统保存内核线程的寄存器状态,并切换到用户线程的执行逻辑。这个切换使得调度器可以在内核的线性逻辑与用户程序的线性逻辑之间来回切换,这是操作系统内核的功能。
从 CPU 的角度看,它仍然是顺序执行的:先执行内核线程,然后由内核切换到用户线程,实现线程切换。因此,从 CPU 和操作系统的角度来看,线程就是这样定义的。
1.3 多线程与并行
从更高层、从编写多线程程序的角度看,多线程的目的通常是并行计算。操作系统可以将这些线程分配给多个 CPU 同时运行。例如,如果我有两颗单线程 CPU,操作系统就能把两个线程分别放在两颗 CPU 上运行。
虽然它们使用不同的寄存器,但可以共享同一个内存空间。例如某个变量存储在内存的某个位置,现在两个线程都需要读它,它们使用的是同一套地址空间。它们可以运行在两个不同的 CPU 上(假设是两颗单线程的原始 CPU;现代 CPU 还有很多技术,一颗 CPU 也能同时运行多个线程)。
2. 进程
2.1 什么是进程?
说完线程,再看进程。进程是操作系统为每个应用程序提供的隔离环境,类似于一个“虚拟机”。为什么说是虚拟机?因为它让每个应用程序感觉自己独占一台计算机,而且这台计算机非常朴素,只有一颗顺序执行的 CPU 和一块专用内存。
进程的本质就是为每个应用程序提供一个隔离环境,就像拥有独立的一台计算机。进程至少有一个线程,对吧?至少有一段连续逻辑,可能是多线程,但至少有一个线程。
根据图灵机的概念,一段代码存放在内存的某个位置。我们先不考虑物理内存的细节,而关注进程为应用程序提供的虚拟内存。
这段内存很朴素:从一个固定地址开始是一段连续代码,下面是从另一个固定地址开始向上增长的堆,再往上是从另一个地址向下增长的栈。这个概念就是堆和栈。进程拥有这样一段内存空间。无论你写的是高级语言还是汇编,都可以把自己看作在这台虚拟机上编写并运行代码。因此当我们讨论堆栈时,指的是这个虚拟空间上的堆栈。
2.2 操作系统如何实现进程?
进程就是这样。那么操作系统如何模拟“独占 CPU 和内存”呢?这是操作系统最基本的功能之一。
首先是 CPU 的调度机制。调度机制的目标是尽量均匀地分配 CPU 时间,让每个应用程序像独占 CPU 一样运行。当一个进程的时间片用完时,硬件触发定时器中断。系统保存寄存器数据,将所有计算相关寄存器切换到调度器,加载下一个进程的寄存器数据,再切换到下一个进程。这个过程对程序员不可见:应用程序被“打晕”后又被唤醒,看到寄存器状态没有变化,于是形成“这段时间什么都没发生”的幻觉,从而感觉自己独占并连续地使用 CPU。
其次是内存的虚拟内存机制。虚拟内存主要通过页表映射实现。物理内存被切成片段,再按规则抽取并组合成连续的虚拟内存供进程使用。物理内存与虚拟内存之间的地址映射关系由内核维护。
因此,进程就像一台拥有独立处理器与独立内存的计算机,让应用程序/程序员可以认为自己是在一台独立的计算机上编写并运行程序。这就是进程,本质上是一种对计算机的模拟或抽象。
3. 线程与进程的对比
3.1 多线程、多进程与并行的关系
多进程指多个应用程序同时运行。无论是多线程还是多进程,都可以在现代计算机上并行运行,但这并不意味着它们总是并行。
比如,如果我只有一颗单线程 CPU,无论写多少线程,它们都无法同时执行,只能轮流使用 CPU。这种情况本质上就是协程(下文会提)。
又或者,即使我有多颗性能很好的 CPU,但操作系统调度器每次只把所有 CPU 资源分配给一个进程,那么在这种情况下,多进程也无法并行。
虽然听起来很奇怪,但理论上是可能的。并行与多线程/多进程并没有必然联系。
一般来说,多线程的目的在于并行计算,而多进程的目的未必是并行。多进程的本质是为多个应用程序提供隔离环境。因此,一个进程内的多线程共享同一套上下文,而多进程拥有不同上下文与内存空间。它们在含义、目的和实现方式上本质不同。
3.2 线程不是“缩小版进程”
我们常听到“一个进程包含多个线程”。这句话容易让人误以为线程是低级进程或微型进程。但这是错的,因为线程和进程是两个不同概念。线程并不是由进程派生出来的,它们是两个不同维度的概念。
4. 协程
最后说一下协程。概念上,协程也是文章开头提到的线性逻辑。
但与多线程不同,协程不支持并行。为什么不能并行?因为并行可能带来**数据竞争(data race)**问题。
所以,多协程指的是在一个线程中运行多条逻辑线,就像前面提到的在单线程 CPU 上进行线程切换。它在物理上是顺序执行的,不存在并行,但在逻辑上有多条执行线。
以上就是我对多进程、多线程与协程区别的理解。谢谢阅读。