“并发”是 Python 里特别容易被说混的一件事。很多人知道线程、进程和协程这几个名词,却不清楚它们各自解决什么问题,也不知道该如何根据任务类型做判断。
这一节我们会先把并发与并行、I/O 密集与 CPU 密集这些前置概念讲清楚,再回到线程、进程和协程本身,建立一套足够实用的选型思路。
并发与并行不是一回事
并发强调的是“多个任务在时间上交错推进”,并行强调的是“多个任务在同一时刻真正同时执行”。
这两者经常被混用,但它们解决的问题并不完全一样。
对于很多后端服务和脚本任务来说,我们更关心的是:当一个任务在等待网络、磁盘或外部系统时,能不能让另一个任务继续往前跑。这通常就是并发问题。
所以学并发的第一步,不是选库,而是先认清你的任务到底在被什么阻塞。
多线程:适合哪些任务?
线程适合 I/O 密集型任务,比如:
- 网络请求;
- 文件读写;
- 数据库等待;
- 调用外部服务。
因为这类场景里,线程大量时间都在等待外部资源,而不是持续占满 CPU。
线程的优势是接入成本相对低,很多阻塞式库也更容易直接配合使用。但线程也有共享内存带来的同步复杂度,所以它不是“开得越多越好”的工具。
多进程:如何绕开 GIL 的限制?
当任务是 CPU 密集型时,比如:
- 图像处理;
- 大量数值计算;
- 批量压缩转换;
- 某些复杂解析任务;
多进程通常会更合适。
因为每个进程都有独立解释器和内存空间,能够更好利用多核资源。
代价则在于:
- 创建成本更高;
- 进程间通信更复杂;
- 状态共享不像线程那样直接。
所以多进程解决的是“CPU 压力”,但引入的是更重的隔离和协调成本。
协程:面向 I/O 的高并发方案
协程常被称为轻量并发单元,它特别适合高并发 I/O 场景。
和线程不同,协程的切换通常由程序在合适位置显式让出控制权,也就是 await 这类操作点。这意味着它很高效,但也要求整个调用链尽量遵守异步模型。
所以协程的优势不只是“轻”,而是它让大量 I/O 等待型任务能在单线程事件循环里高效调度。
不过,一旦中间混入阻塞式调用,整个异步链条就可能被卡住,这也是它和线程模型很不一样的地方。
线程、进程与协程的选择准则
一个非常实用的判断方式是:
- 主要在等外部资源,优先考虑线程或协程;
- 主要在吃 CPU,优先考虑进程;
- 已有同步阻塞式库很多,线程往往更容易接入;
- 整个链路都能异步化,而且并发量较高,协程通常更有优势。
所以选型的核心不是“谁更高级”,而是“当前任务的阻塞形态和项目约束是什么”。
常见误区:不是并发越多越好
并发并不是免费午餐。
线程太多会带来上下文切换和共享状态问题;进程太多会吃掉更多内存和调度成本;协程太多也可能把外部资源打满,或者让系统难以定位瓶颈。
所以并发设计真正成熟的标志,不是你会开多少任务,而是你知道什么时候该收敛、限流、隔离和监控。
总结与预告
这一节我们先把 Python 并发世界里的几个核心参与者摆到了同一张图上。会不会并发,不只是会调用某个库,而是能不能先判断任务特征,再选对模型。
下一节我们会继续聚焦协程和异步编程,把 asyncio 的事件循环、任务调度和异常处理真正讲透。