进程 vs. 线程
日常用电脑时,你一定经历过这样的场景:后台挂着游戏更新,浏览器里打开 3 个编程文档、1 个在线音乐页面,同时还在微信摸鱼刷消息——这就是典型的多任务并行处理。
今天我们就来拆解实现多任务的三位核心角色:进程、线程,还有在高并发场景下越来越受欢迎的协程。
我们会对比它们的特点、优缺点和适用场景,最后再用 Python 写几段极简代码,让你一眼看清它们的区别。
1. 多任务的通用架构:Master‑Worker 模式
不管底层用的是多进程、多线程还是协程,大多数系统实现多任务的思路都离不开 Master‑Worker 分工协作:
- Master(监工):负责任务接收、拆分、分配,有时也做全局协调。
- Worker(工人):接到任务后立刻执行。
- 常规配置:一个 Master 对应多个 Worker。
具体到不同实现:
- 多进程:主进程当 Master,子进程当 Worker。
- 多线程:主线程当 Master,子线程当 Worker。
- 协程:主线程(或单个进程)内的事件循环当 Master,协程当 Worker。
2. 传统双雄:多进程 vs 多线程
多进程模式
我们可以把进程理解为一个独立的“工作车间”:每个车间都有自己的仓库(内存空间)、工具库(文件句柄、网络连接等)。车间之间完全物理隔离,除非专门修一条“传送带”(进程间通信,IPC),否则不能随便拿对方的东西。
优点
- 稳定性天花板:某个子车间(子进程)炸了(崩溃),只会收拾它自己的摊子,主车间和其他子车间完全不受影响。这也是早期 Apache、Chrome 浏览器早期架构采用纯多进程的原因。
- 彻底利用多核 CPU:每个车间可以独占一个 CPU 核心干活,不受调度限制(Windows / Linux 都能做到)。
- 绝对的内存隔离:完全不用担心其他车间“偷拿”或“污染”自己的数据。
缺点
- 车间建造成本高:申请独立仓库、工具库都需要时间和系统资源。Windows 系统下的创建成本尤其大,甚至是 Linux 的数倍到数十倍。
- 切换成本大:车间主任(CPU 核心)从 A 车间转到 B 车间,得先把 A 的所有工具、进度条收好,再把 B 的拿出来摆好,这个过程叫上下文切换。
- IPC 比较麻烦:车间之间传文件、数据得用专用的传送带(管道、消息队列、共享内存等)。如果使用共享内存,还得加“安全门”(锁)防止拿错,代码复杂度会明显上升。
多线程模式
线程则是车间里的“工人小组”:所有小组共享同一个仓库、工具库,但每个小组有自己的工作桌、进度条(寄存器、栈空间)。小组之间沟通,直接递纸条就行(共享内存),不需要额外的传送带。
优点
- 小组组建快:不需要申请独立仓库,直接在现有车间里划一块区域当工作桌就行,创建和切换成本远低于进程。
- 小组间沟通简单:递纸条(共享全局变量、堆内存)效率极高。
- 适合 I/O 等待场景:比如某个小组在等快递(等网络请求、等磁盘读写),车间主任可以立刻切换到另一个空闲小组干活,CPU 不会闲着。
缺点
- 稳定性较差:某个小组不小心碰断了车间的主电源(触发非法内存访问),整个车间(整个进程)都会断电。这也是很多早期 Web 服务器不敢使用纯多线程的原因。
- 需要处理“抢仓库”的问题:多个小组同时用同一把螺丝刀(修改同一个全局变量),会把数据改乱,这叫竞态条件。解决办法是加“工具使用登记本”(锁)来规范,但锁用多了会让效率下降,甚至出现“死锁”(两个小组各拿对方需要的螺丝刀不放)。
- Python 的特殊限制:CPython(我们最常用的 Python 解释器)里有个 GIL(全局解释器锁):同一时间只能有一个小组(线程)在车间的“主操作区”(执行 Python 字节码)干活。这意味着纯 Python 写的多线程,无法真正利用多核 CPU 并行处理计算任务。
3. 现代服务器的妥协:混合模式
纯多进程或纯多线程都有各自的硬伤,所以主流服务器软件早就用上了 “多进程保底 + 多线程提效” 的混合模式:
- Apache:支持三种多处理模块(MPM)
prefork:纯多进程,适合对稳定性要求极高的场景。worker:多进程打底,每个子进程里开多个线程,平衡了稳定性和资源利用率。event:在 worker 的基础上优化了 I/O 等待,性能更高。
- IIS:默认多线程,但也支持进程隔离模式。
- Nginx:走得更远,直接用了 单 Master + 多 Worker 进程 + 事件驱动 的异步模型,性能在很多场景下碾压 Apache 和 IIS 的传统模式。
4. 别让“换工人”拖垮效率:任务切换的坑
不管是切换进程、线程,还是后面要说的协程,上下文切换本身都是有成本的:
- 保存当前任务的进度条、工具状态。
- 准备下一个任务的进度条、工具状态。
- 把下一个任务放到 CPU 核心上开始执行。
如果任务开得太多,系统可能会把 80% 以上的时间花在切换上,只有 20% 的时间在真正干活。
CPU 核心利用率看起来很高,但实际业务处理速度很慢,甚至会出现“系统假死”(鼠标键盘动不了,但后台进程还在跑)。
5. 选对人干对活:任务类型与方案匹配
在选择多任务方案前,先搞清楚你的任务是 “CPU 忙” 还是 “闲等多”。
计算密集型任务(CPU 忙)
- 特点:大部分时间都在 CPU 上运算,几乎不闲等。
典型例子:科学计算、视频编解码、密码学哈希 / 加密、机器学习训练推理。 - 建议:
- 任务数 ≈ CPU 核心数(可以多 1~2 个,用来处理突发的系统调度)。
- 优先用 C / C++ / Rust / Golang 这些没有全局解释器锁、编译后效率极高的语言。
- 如果非要用 Python,选多进程(可以绕开 GIL),或者用 C 扩展(比如 NumPy、PyTorch 底层都是 C 写的,不受 GIL 限制)。
I/O 密集型任务(闲等多)
- 特点:大部分时间都在“闲等”:等网络请求返回、等数据库读写、等用户输入。
CPU 核心利用率很低(可能只有 10%~30%)。
典型例子:Web 服务、爬虫、数据库中间件、聊天软件后端。 - 建议:
- 任务数可以适当开多(比如 CPU 核心数的 5~10 倍,甚至更多)。
- Python / JavaScript 这些解释型脚本语言完全够用,开发效率比执行效率重要得多。
- 现代趋势是用异步 I/O + 协程(后面会详细讲),比多线程效率更高、开销更低。
6. 高并发场景的新宠:异步 I/O + 协程
异步 I/O
现代操作系统(Linux 的 epoll、Windows 的 IOCP、macOS 的 kqueue)提供了高效的异步 I/O 支持:不需要专门开多个线程 / 进程去等待 I/O,只需要告诉操作系统“等这个网络请求回来后喊我一声”,然后主线程 / 单个进程就可以去干别的事了。
典型代表:Nginx、Node.js、Redis(主进程是单线程的,但性能极高)。
协程
协程可以理解为 “轻量级到极致的工人小组”:所有协程都在同一个线程里,由程序自己写的调度器(事件循环) 控制切换,不需要操作系统插手。
切换一个协程的成本有多低?大概是切换线程的 千分之一甚至万分之一——只需要保存和恢复几个指针就行。
Python 里的协程语法是 async / await,配套的标准库是 asyncio,还有第三方库 gevent(对同步代码更友好)。
优势
- 并发能力拉满:单个线程里可以轻松开几万甚至几十万个协程(多线程最多开几千个就会因为栈空间不够崩溃)。
- 几乎没有切换成本。
- 不需要处理复杂的线程锁问题(因为只有一个线程在执行协程,同一时间只有一个协程在运行“主操作区”的代码)。
7. 一张表搞定选择
8. Python 实现示例(极简版)
多进程
运行结果大约总耗时 3s(三个子进程并行执行)。
多线程
因为是 I/O 密集型,总耗时也是 3s 左右(GIL 在 time.sleep 时会释放)。
协程
总耗时同样是 3s 左右,但资源利用率比多线程高得多。
9. 最后总结
- 先分任务类型:CPU 密集型找多进程 / C 扩展,I/O 密集型找协程 / 异步框架。
- 不要过度设计:如果只是简单的后台定时任务,线程池就足够了。
- Python 特殊情况:永远别忘了 GIL 的存在。
- 关注现代趋势:异步 I/O 和协程已经是高并发 Web 服务的主流方案。
选择最适合你需求的,而不是看起来“最厉害”的,这才是好的架构设计。

