计算机科学

进程 vs. 线程:技术面试第一高频问题,从内存结构彻底理解

打开任务管理器会看到进程列表,阅读开发文档时又会遇到线程。它们看起来都是“正在运行的某种东西”,但准确来说有什么区别?

4 分钟阅读
进程 vs. 线程:技术面试第一高频问题,从内存结构彻底理解 封面图

打开任务管理器会看到进程列表,阅读开发文档时又会遇到线程。它们看起来都是“正在运行的某种东西”,但准确来说有什么区别?

如果只能选一个技术面试中最常见的问题,那一定是这个:“请解释进程和线程的区别。”

它成为经典问题的原因很简单:只靠这一题,就能连续考察内存结构、操作系统和并发。

本文将从内存结构的角度梳理这两个概念的区别,并介绍面试时回答到什么程度比较合适。

先从核心内容开始。

  1. 进程(Process):正在运行的程序。拥有完整的独立内存空间
  2. 线程(Thread):进程中的执行流。只有栈各自独立,其余部分共享
  3. 线程因为共享内存而轻量、快速,但也因此会产生数据竞争
  4. 进程彼此隔离且安全,但相应地更重,通信也更麻烦

进程:正在运行的程序

程序是存储在磁盘上的一段代码,目前还没有执行任何操作。

执行程序的瞬间,操作系统会将它加载到内存,并准备分配 CPU。这个“正在运行的程序”就是进程。

操作系统会为每个进程提供完整且独立的虚拟内存空间,整体大致分为四个区域。

区域 内容
代码(Text) 要执行的机器码
数据(Data) 全局变量、静态变量
堆(Heap) 运行时动态分配的内存
栈(Stack) 函数调用信息、局部变量

这里重要的是“独立”这个词。进程 A 无法查看进程 B 的内存,操作系统会从根本上阻止这种访问。

所以即使一个进程终止,其他进程也能正常运行。Chrome 为每个标签页启动独立进程就是这个原因;即使一个标签页崩溃,整个浏览器仍然存活。


线程:进程中的执行流

线程是进程中实际执行代码的执行流单位。每个进程至少从一个线程(主线程)开始。

关键在于共享范围。同一进程中的线程共享代码、数据和堆区域,只有栈由各自独立持有。

只有栈各自独立,代码、数据和堆共享的结构
只有栈各自独立,代码、数据和堆共享的结构

栈需要各自独立的原因很明确。栈记录“当前执行哪个函数、执行到哪一步”,因此每个执行流都需要一个栈。

另一方面,由于堆是共享的,线程之间只需通过同一个变量就能直接交换数据,不需要额外的通信流程。


为什么使用线程:成本问题

你可能会想:“想同时做多件事,启动多个进程不就行了吗?”但问题在于成本。

创建成本。创建进程需要准备完整的独立内存空间,而线程只需额外添加一个栈。

切换成本。CPU 切换到另一个进程时,必须替换整个内存映射(上下文切换)。同一进程内的线程切换要轻量得多。

通信成本。进程之间交换数据时,必须经过管道、套接字等 IPC(进程间通信)流程。线程只需读取同一个变量。

总之,线程是以较低成本同时启动多个任务的单位。


并非没有代价:数据竞争

线程的所有优点都来自共享内存,而它最大的弱点也恰恰来自同一个地方。

两个线程同时写入同一个变量时,结果无法预测。这就是数据竞争。

即使是 count += 1 这样的一行代码,内部也分为读取 → 加法 → 写入三个步骤;两个线程发生重叠时,递增操作可能真的会丢失。

因此需要锁、信号量等同步工具,但如果使用不当,就会打开另一个地狱:死锁。

进程从一开始就隔离了内存,因此没有这类问题。这就是安全性与效率之间的权衡。

多个线程同时访问共享内存的瞬间,数据竞争就开始了
多个线程同时访问共享内存的瞬间,数据竞争就开始了

从 iOS 开发者的角度来看

在 iOS 中,一个应用就是一个进程。由于沙盒策略,应用无法访问其他应用的内存。上面提到的进程隔离在这里直接适用。

在应用内部,主线程负责 UI,网络操作和繁重计算则发送到其他线程。“UI 更新要在主线程执行”这一规则正是建立在线程概念之上的。

不过,在如今的 iOS 开发中,直接创建线程并不常见。把任务提交给 GCD 队列或 Swift Concurrency 的 Task 后,系统会自动从线程池中进行分配。只是抽象层提升了一层,底层仍然是线程在运行。


面试时可以这样回答

最好先从一句话的回答开始。

“进程是拥有独立内存空间的执行单元,而线程是进程中的执行流,只有栈各自独立,其余内存都共享。”

接下来的追问几乎都可以预料:“线程共享什么、各自持有什么?”(只有栈)、“为什么线程更轻量?”(创建、切换、通信成本)、“共享会带来什么问题?”(数据竞争与同步)。能回答到这里就达到及格线了。

再补充一个 Chrome 标签页或 iOS 沙盒这样的实际案例,就能让面试官感受到你是在理解,而不是背诵。


总结

  • 进程是正在运行的程序,并从操作系统获得独立的内存空间(代码、数据、堆、栈)
  • 线程是进程中的执行流,只有栈各自独立,代码、数据和堆则共享
  • 线程的创建、切换和通信成本低,因此轻量;但由于共享内存,存在数据竞争风险
  • 进程彼此隔离且安全,但比较沉重,通信需要 IPC
  • 在 iOS 中,一个应用是一个进程,而 GCD 和 Task 是建立在线程之上的抽象
  • 面试回答框架:一句话定义 → 共享范围 → 成本差异 → 数据竞争