Skip to content

进程、线程、协程区别

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 下这三者画成一个层次:

  1. 最底层是 CPU 和内核调度器。
  2. 内核直接调度的是 task,也就是进程 / 线程这类实体。
  3. 协程运行在用户态,最终必须挂靠在某个线程上。

所以:

  • 进程 / 线程是 Linux 内核层面的并发实体
  • 协程是用户态层面的并发抽象

这也是为什么你经常会看到:

  • 一个进程
  • 里面有多个线程
  • 每个线程上跑大量协程

这三者不是互斥关系,而是可以叠加。

11. 应该怎么选:进程、线程、协程

11.1 什么时候优先用进程

优先考虑进程的场景:

  • 服务需要强隔离
  • 崩溃不能互相影响
  • 权限边界不同
  • 希望和 OS 资源控制机制天然结合

例如:

  • Web 服务和数据库分开进程
  • 浏览器标签页隔离
  • 容器化服务

11.2 什么时候优先用线程

优先考虑线程的场景:

  • 要利用多核 CPU
  • 共享内存带来的效率更重要
  • 任务是通用并发,不一定全是 I/O

例如:

  • 线程池处理并发任务
  • 图像处理、并行计算
  • 通用服务端业务执行

11.3 什么时候优先用协程

优先考虑协程的场景:

  • 连接数很多
  • I/O 等待时间长
  • 任务数量远大于线程数
  • 希望降低线程和上下文切换成本

例如:

  • 网关
  • RPC 框架
  • 爬虫
  • 长连接服务

11.4 协程不是线程的“完全替代品”

这是一个很重要的误区。

协程并不能完全替代线程,因为:

  • 协程最终还是要跑在线程上
  • 真正利用多核仍需要多个线程
  • 阻塞调用处理不好,协程一样会卡住线程

所以更准确的说法是:

  • 协程是对线程模型的补充,而不是彻底取代