进程、线程、协程区别
1. 先给结论:进程、线程、协程分别是什么
可以先用一句话概括:
- 进程:资源分配的基本单位。
- 线程:CPU 调度的基本单位。
- 协程:用户态自己调度的轻量执行单元。
2. 进程是什么
2.1 进程的本质
进程(Process)可以理解为:
- 一个正在运行的程序实例
它不是单纯的“代码文件”,而是:
- 代码
- 数据
- 地址空间
- 打开的文件
- 信号处理信息
- 权限信息
- 各种内核资源
的整体集合。
所以操作系统眼里的进程,本质上是:
- 资源拥有者
2.2 进程为什么重要
进程最大的价值是隔离。
不同进程之间通常拥有:
- 不同的虚拟地址空间
- 不同的文件描述符表副本或引用关系
- 不同的权限边界
这意味着:
- 一个进程崩溃,不一定会直接把另一个进程带崩
- 一个进程一般不能直接随便读写另一个进程的内存
这也是为什么现代系统喜欢把:
- Web 服务
- 数据库
- 消息队列
- 浏览器标签页
拆到不同进程里。
3. 线程是什么
3.1 线程的本质
线程(Thread)是进程内部的一个执行流。
一个进程至少有一个线程,也可以有多个线程。
同一进程内的多个线程通常共享:
- 地址空间
- 代码段
- 堆
- 全局变量
- 打开的文件描述符
但每个线程自己有:
- 程序计数器(PC)
- 寄存器上下文
- 栈
- 线程局部存储(TLS)
所以线程的本质是:
- 共享大部分资源,但各自独立执行
3.2 为什么线程比进程更轻量
因为线程不需要像进程那样重新创建一整套独立资源空间。
线程切换和创建成本通常比进程低,原因包括:
- 不需要切换完整地址空间
- 不需要重新建立大量资源对象
- 同进程内数据共享更直接
这也是为什么高并发服务通常更愿意优先使用线程,而不是“每个任务一个进程”。
4. 协程是什么
4.1 协程的本质
协程(Coroutine)不是操作系统内核直接调度的实体。
它更准确地说是:
- 用户态运行时管理的轻量任务
也就是说:
- 内核并不知道“协程”这个对象
- 内核只知道线程
- 协程是在某个线程内部,由用户态框架自己切换的执行单元
4.2 协程为什么轻
因为协程切换通常不需要:
- 内核态参与
- 系统调用
- 线程上下文切换
协程切换本质上通常只是:
- 保存少量上下文
- 切到另一个用户态任务继续执行
所以协程的切换成本通常远低于线程。
4.3 协程适合什么问题
协程特别适合:
- 高并发
- 大量 I/O 等待
- 任务很多但 CPU 计算不重
典型场景:
- 网络服务器
- 爬虫
- 网关
- RPC 框架
- 异步数据库访问
5. 在 Linux 系统设计里,进程是怎么实现的
5.1 Linux 对进程的抽象
在 Linux 里,进程不是一个纯概念对象,而是内核中的任务实体。
内核会为运行实体维护:
task_struct
它里面记录了这个任务相关的大量信息,例如:
- 调度信息
- 状态
- 内存描述符
- 打开的文件
- 信号
- 父子关系
- 权限信息
所以从 Linux 内核实现角度看:
- 进程是一个带完整资源上下文的任务
5.2 Linux 中进程最关键的资源
进程最关键的资源包括:
- 虚拟地址空间
-
由页表和内存描述符管理
-
文件描述符表
-
管理打开文件、socket、pipe 等
-
信号处理上下文
-
用户/权限信息
-
当前工作目录、命名空间、cgroup 等运行环境信息
这些资源一起定义了:
- “这个进程拥有什么”
5.3 Linux 为什么把进程设计成资源边界
因为进程天然适合作为:
- 故障隔离边界
- 安全隔离边界
- 资源统计与限制边界
这也是 Linux 后来的很多机制都围绕进程扩展:
- namespace
- cgroup
- 容器
本质上还是在强化“进程作为隔离单元”的能力。
6. 在 Linux 系统设计里,线程是怎么实现的
6.1 Linux 里线程和进程并不是两套完全不同的内核对象
这是 Linux 非常重要的设计点。
在 Linux 内核里:
- 线程本质上也是一种 task
- 也用
task_struct表示
也就是说,Linux 没有为“线程”单独发明一套完全不同的数据结构体系。
更准确地说:
- 线程可以看成共享部分资源的轻量进程
6.2 Linux 中线程是怎么创建的
Linux 下线程通常基于:
clone()
创建。
fork() 和 clone() 的区别可以粗略理解为:
fork()- 创建新进程
-
子进程复制或继承父进程上下文,但拥有独立资源边界
-
clone() - 可以精细控制“哪些资源共享,哪些资源独立”
例如可以选择共享:
- 地址空间
- 文件描述符表
- 信号处理器
当多个执行实体共享同一个地址空间和资源集合时,从用户视角看,它们就是线程。
6.3 Linux 线程共享什么,不共享什么
同一进程下的线程通常共享:
- 虚拟地址空间
- 代码段
- 堆
- 全局变量
- 文件描述符
每个线程自己独有:
- 线程栈
- 寄存器状态
- 程序计数器
- 调度状态
- 线程 id
所以 Linux 的线程模型可以理解为:
- 共享资源
- 独立调度
6.4 Linux 为什么这样设计线程
这样设计的好处是:
- 内核统一管理“任务”
- 调度模型更简单
- 线程和进程复用大量基础设施
- 用户态可以通过共享标志灵活构造线程模型
这也是 Linux 常被总结为:
- 线程就是共享资源的轻量级进程
7. 在 Linux 系统设计里,协程是什么位置
7.1 协程不是 Linux 内核调度对象
Linux 内核调度器调度的是:
- 进程 / 线程对应的 task
不是协程。
所以从 Linux 内核视角看:
- 协程根本不是一级公民
协程通常存在于:
- 用户态语言运行时
- 用户态框架
- 用户态库
例如:
- Go goroutine
- Python
asyncio - Kotlin coroutine
- C++ 用户态协程库
7.2 协程如何在 Linux 上运行
协程最终一定要落到线程上运行。
也就是说:
- 协程不能脱离线程单独被 CPU 执行
- 它只是线程内部的一种任务调度方式
典型情况有两类:
- 单线程协程
-
一个线程上跑大量协程
-
多线程 + 协程
- 多个线程组成调度器
- 每个线程执行若干协程
7.3 协程的切换为什么便宜
因为协程切换通常发生在用户态:
- 不需要陷入内核
- 不需要 OS 调度
- 不需要切换完整线程上下文
所以它能把大量 I/O 等待任务压缩进很少的线程里运行。
7.4 协程的前提是什么
协程不是“白送的”,它依赖一些基础条件:
- 运行时调度器
- 非阻塞 I/O 或异步 I/O
- 事件循环或网络多路复用
如果协程里直接做阻塞系统调用,而运行时又没正确托管,就可能把整个线程卡死。
所以协程不是魔法,它只是:
- 把调度权从内核部分转移到用户态
8. 进程、线程、协程的核心区别
8.1 从资源拥有角度看
- 进程
-
拥有独立资源边界
-
线程
-
不独占大部分资源,资源属于进程
-
协程
- 几乎不拥有内核资源,只是线程内部的任务
8.2 从调度者角度看
- 进程
-
由内核调度
-
线程
-
由内核调度
-
协程
- 由用户态调度器调度
8.3 从切换成本角度看
一般来说:
- 进程切换最重
- 线程切换次之
- 协程切换最轻
原因是:
- 进程切换涉及更完整的资源上下文
- 线程切换需要内核调度
- 协程切换通常只在用户态保存少量上下文
8.4 从内存隔离角度看
- 进程
-
隔离最强
-
线程
-
同进程内共享地址空间,隔离弱
-
协程
- 共享线程和进程环境,隔离更弱
8.5 从通信方式角度看
- 进程
- 通信通常要走 IPC
-
如 pipe、socket、共享内存、消息队列
-
线程
- 直接读写共享内存
-
但要处理锁、可见性、竞态问题
-
协程
- 通常共享同一线程上下文
- 逻辑通信成本更低
- 但依然要考虑并发语义和资源竞争
8.6 从使用场景角度看
- 进程
-
强隔离、多服务拆分、故障隔离
-
线程
-
多核计算、通用并发、线程池
-
协程
- 高并发 I/O、海量连接、异步编程
9. 区别
| 维度 | 进程 | 线程 | 协程 |
|---|---|---|---|
| 本质 | 资源分配单位 | CPU 调度单位 | 用户态轻量任务 |
| 是否由内核直接感知 | 是 | 是 | 否 |
| 调度者 | 内核 | 内核 | 用户态运行时 |
| 地址空间 | 独立 | 同进程共享 | 跟随所属线程 |
| 是否共享堆和全局变量 | 否 | 是 | 是 |
| 是否有独立栈 | 是 | 是 | 通常有自己的逻辑栈/上下文 |
| 切换成本 | 高 | 中 | 低 |
| 隔离性 | 强 | 中 | 弱 |
| 通信成本 | 高 | 低 | 更低 |
| 典型场景 | 服务隔离、容器、独立程序 | 线程池、并行计算 | 高并发 I/O、异步运行时 |
10. Linux 设计里三者的关系怎么理解
可以把 Linux 下这三者画成一个层次:
- 最底层是 CPU 和内核调度器。
- 内核直接调度的是 task,也就是进程 / 线程这类实体。
- 协程运行在用户态,最终必须挂靠在某个线程上。
所以:
- 进程 / 线程是 Linux 内核层面的并发实体
- 协程是用户态层面的并发抽象
这也是为什么你经常会看到:
- 一个进程
- 里面有多个线程
- 每个线程上跑大量协程
这三者不是互斥关系,而是可以叠加。
11. 应该怎么选:进程、线程、协程
11.1 什么时候优先用进程
优先考虑进程的场景:
- 服务需要强隔离
- 崩溃不能互相影响
- 权限边界不同
- 希望和 OS 资源控制机制天然结合
例如:
- Web 服务和数据库分开进程
- 浏览器标签页隔离
- 容器化服务
11.2 什么时候优先用线程
优先考虑线程的场景:
- 要利用多核 CPU
- 共享内存带来的效率更重要
- 任务是通用并发,不一定全是 I/O
例如:
- 线程池处理并发任务
- 图像处理、并行计算
- 通用服务端业务执行
11.3 什么时候优先用协程
优先考虑协程的场景:
- 连接数很多
- I/O 等待时间长
- 任务数量远大于线程数
- 希望降低线程和上下文切换成本
例如:
- 网关
- RPC 框架
- 爬虫
- 长连接服务
11.4 协程不是线程的“完全替代品”
这是一个很重要的误区。
协程并不能完全替代线程,因为:
- 协程最终还是要跑在线程上
- 真正利用多核仍需要多个线程
- 阻塞调用处理不好,协程一样会卡住线程
所以更准确的说法是:
- 协程是对线程模型的补充,而不是彻底取代