进程、线程、协程 核心知识点
进程、线程、协程是并发编程的核心抽象,三者从资源粒度 、调度方式 、开销成本上层层递进,适配不同的并发场景。
进程(Process)
定义
进程是操作系统资源分配的基本单位,是程序的一次动态执行过程(包含程序代码、数据、内存空间、文件句柄等资源)。每个进程拥有独立的地址空间,与其他进程相互隔离。
核心特征
| 特征 | 说明 |
|---|---|
| 独立性 | 进程拥有独立的内存空间、文件描述符、寄存器等,一个进程崩溃不会影响其他进程(受内核保护) |
| 资源独占 | 系统为每个进程分配独立的内存(如堆、栈、数据段),进程间默认无法直接共享数据 |
| 调度单位 | 由操作系统内核调度(CPU调度),调度时需切换内存映射、页表等,开销极大 |
| 并发/并行 | 多进程可并行(多核CPU)或并发(单核CPU时间片轮转)执行 |
进程的生命周期
- 创建态 :进程被创建(如
fork()/CreateProcess()),系统分配资源但未就绪; - 就绪态:资源分配完成,等待CPU调度;
- 运行态:占用CPU,执行程序代码;
- 阻塞态:等待I/O、信号量等事件,主动放弃CPU;
- 终止态:进程执行完毕/异常终止,系统回收资源。
进程间通信(IPC)
因进程地址空间隔离,需通过内核中转通信:
- 管道(匿名管道/命名管道):半双工,适用于父子进程/同机进程;
- 消息队列:内核维护的消息链表,可按类型收发;
- 共享内存:最快的IPC方式,多个进程映射同一块物理内存(需加锁同步);
- 信号量:用于进程间同步(控制资源访问);
- 信号:异步通知机制(如
SIGKILL终止进程); - 套接字(Socket):跨网络/跨机进程通信(TCP/UDP)。
优缺点
| 优点 | 缺点 |
|---|---|
| 稳定性高(崩溃不扩散) | 创建/切换开销极大 |
| 隔离性好 | 资源占用高(内存/CPU) |
| 可利用多核CPU | 通信复杂、同步成本高 |
典型应用
- 独立运行的程序(如浏览器、微信、MySQL服务);
- 计算密集型任务(如视频渲染,多进程利用多核)。
线程(Thread)
定义
线程是操作系统CPU调度的基本单位,也叫"轻量级进程",隶属于进程,共享进程的地址空间(代码段、堆、文件句柄等),仅拥有独立的栈空间、寄存器和程序计数器。
核心特征
| 特征 | 说明 |
|---|---|
| 资源共享 | 同一进程内的所有线程共享进程资源(如内存、数据库连接),无需IPC即可通信 |
| 轻量级 | 线程创建/切换仅需保存栈和寄存器,开销远低于进程 |
| 调度方式 | 内核抢占式调度(内核可随时中断线程,分配CPU给其他线程) |
| 并发粒度 | 更细(单进程内多线程可同时执行不同任务) |
| 依附性 | 线程不能独立存在,进程终止则所有线程终止 |
线程的分类
- 内核线程 :由操作系统内核创建和管理(如Linux的
pthread),可利用多核; - 用户线程:用户态库管理,内核不可见(已被淘汰);
- 轻量级线程(LWP):内核线程与用户线程的映射(1:1/多:1/多:多),主流为1:1(如Java的Thread)。
线程同步机制
因共享资源,多线程易出现"竞态条件",需同步控制:
- 互斥锁(Mutex):保证同一时间只有一个线程访问临界资源;
- 自旋锁:线程忙等锁释放(适用于锁持有时间短的场景);
- 读写锁(RWLock):读共享、写独占,提升读密集型场景效率;
- 条件变量:线程间的等待-唤醒机制(如生产者-消费者模型);
- 信号量:控制同时访问资源的线程数;
- 原子操作:无锁同步(如
AtomicInteger,基于CPU指令)。
线程的问题
- 线程安全:多线程操作共享数据时,未同步导致数据错乱(如计数器值异常);
- 死锁:多个线程互相持有对方需要的锁,导致永久阻塞;
- 上下文切换:内核调度线程时的上下文保存/恢复,高并发下开销显著;
- GIL(Python特有):全局解释器锁,导致Python多线程无法利用多核(仅能并发,不能并行)。
优缺点
| 优点 | 缺点 |
|---|---|
| 开销远低于进程 | 稳定性差(一个线程崩溃致进程终止) |
| 通信简单(共享内存) | 需处理同步/死锁问题 |
| 可利用多核(无GIL) | 切换仍有内核态开销 |
典型应用
- 单进程内的并发任务(如Web服务器的请求处理线程);
- I/O密集型任务(如同时处理多个网络请求);
- 图形界面程序(UI线程+后台任务线程)。
协程(Coroutine)
定义
协程是用户态的轻量级线程,也叫"微线程",由程序/框架(而非内核)调度,运行在单线程内,通过主动让出CPU实现多任务并发。
核心特征
| 特征 | 说明 |
|---|---|
| 用户态调度 | 调度逻辑由用户代码实现(如asyncio、Go GPM),无内核态切换开销 |
| 非抢占式 | 协程需主动让出CPU(如await/yield),其他协程才能执行 |
| 资源共享 | 同一线程内的协程共享线程资源,无需锁(单线程内串行执行) |
| 极致轻量化 | 初始栈仅KB级(Go goroutine 2KB),单机可创建百万级协程 |
| 绑定线程 | 协程运行在固定线程内,默认无法利用多核(需结合多线程/多进程) |
协程的核心模型
| 模型 | 代表语言/框架 | 说明 |
|---|---|---|
| 异步回调 | JavaScript(早期)、Python(twisted) | 基于回调函数实现协程调度,易出现"回调地狱" |
| 生成器协程 | Python(yield/yield from) | 基于生成器暂停/恢复机制,手动调度协程 |
| 原生协程 | Python(async/await)、Go | 语言层面原生支持,由运行时/框架自动调度 |
| 虚拟线程 | Java 21+(Virtual Thread) | JVM层面的协程,映射到内核线程,透明调度 |
主流协程实现
Python协程(asyncio)
- 核心关键字:
async def定义协程函数,await挂起协程(等待I/O); - 调度器:
asyncio事件循环(Event Loop),单线程内调度协程; - 限制:受GIL影响,单线程协程无法利用多核,需结合
multiprocessing。
Go goroutine
- 核心关键字:
go创建协程,无需手动写异步语法; - 调度模型:GPM(G-协程、P-逻辑处理器、M-内核线程),自动将协程映射到多核;
- 优势:语言内置,调度透明,支持千万级并发。
Java虚拟线程(Virtual Thread)
- 由JVM调度,而非操作系统;
- 虚拟线程挂载到平台线程(内核线程),I/O阻塞时自动让出平台线程,避免资源浪费;
- 兼容现有同步代码,无需改写为异步。
协程的调度流程(以Python asyncio为例)
- 定义协程函数(
async def); - 创建事件循环(
asyncio.get_event_loop()); - 将协程任务加入事件循环;
- 事件循环调度协程:遇到
await则挂起当前协程,执行其他就绪协程; - I/O完成后,协程被唤醒,继续执行;
- 所有任务完成,事件循环退出。
优缺点
| 优点 | 缺点 |
|---|---|
| 切换开销极致低 | 不适合CPU密集型任务(单线程串行) |
| 无锁同步(单线程) | 协作式调度,若协程死循环会阻塞线程 |
| 百万级并发支持 | 部分语言支持不完善(如Java 21前) |
| 代码可读性高(async/await) | 调试难度高于线程(用户态调度) |
典型应用
- 高并发I/O密集型任务(如API网关、爬虫、即时通讯服务器);
- 微服务中的异步任务(如消息消费、定时任务);
- 高吞吐网络服务(如Redis、Nginx的协程版)。
进程、线程、协程 核心对比表
| 维度 | 进程 | 线程 | 协程 |
|---|---|---|---|
| 资源分配单位 | 操作系统 | 无(共享进程资源) | 无(共享线程资源) |
| 调度单位 | 内核(抢占式) | 内核(抢占式) | 用户态(协作式) |
| 切换开销 | 极大(内存/页表切换) | 较大(内核态上下文) | 极小(用户态上下文) |
| 内存占用 | 高(MB级) | 中(KB级) | 极低(字节/KB级) |
| 并发规模 | 数十~数百 | 数千~上万 | 百万级 |
| 通信方式 | IPC(管道/共享内存) | 共享内存+锁 | 直接共享(单线程) |
| 稳定性 | 高(崩溃不扩散) | 中(线程崩溃致进程终止) | 中(协程崩溃致线程终止) |
| 多核利用 | 天然支持 | 天然支持(无GIL) | 需结合多线程/进程 |
| 典型场景 | 独立程序/计算密集 | 单进程内并发/I/O密集 | 超高并发I/O密集 |
选型建议
- CPU密集型任务:优先多进程(利用多核),如视频编码、大数据计算;
- 普通I/O密集型任务:多线程(开发成本低),如常规Web服务器;
- 超高并发I/O密集型任务:协程(如百万级网络连接),如直播服务器、爬虫;
- 跨平台/兼容性要求高:线程(成熟稳定);
- Python场景:I/O密集用协程(asyncio),CPU密集用多进程(multiprocessing)。