打开任务管理器会看到进程列表,阅读开发文档时又会遇到线程。它们看起来都是“正在运行的某种东西”,但准确来说有什么区别?
如果只能选一个技术面试中最常见的问题,那一定是这个:“请解释进程和线程的区别。”
它成为经典问题的原因很简单:只靠这一题,就能连续考察内存结构、操作系统和并发。
本文将从内存结构的角度梳理这两个概念的区别,并介绍面试时回答到什么程度比较合适。
先从核心内容开始。
- 进程(Process):正在运行的程序。拥有完整的独立内存空间
- 线程(Thread):进程中的执行流。只有栈各自独立,其余部分共享
- 线程因为共享内存而轻量、快速,但也因此会产生数据竞争
- 进程彼此隔离且安全,但相应地更重,通信也更麻烦
进程:正在运行的程序
程序是存储在磁盘上的一段代码,目前还没有执行任何操作。
执行程序的瞬间,操作系统会将它加载到内存,并准备分配 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 是建立在线程之上的抽象
- 面试回答框架:一句话定义 → 共享范围 → 成本差异 → 数据竞争

