返回专题首页

Python 专题

并发基础:线程、进程与协程到底该怎么选

“并发”是 Python 里特别容易被说混的一件事。很多人知道线程、进程和协程这几个名词,却不清楚它们各自解决什么问题,也不知道该如何根据任务类型做判断。

Python 专题第 28 篇 / 39 篇4 分钟

“并发”是 Python 里特别容易被说混的一件事。很多人知道线程、进程和协程这几个名词,却不清楚它们各自解决什么问题,也不知道该如何根据任务类型做判断。

这一节我们会先把并发与并行、I/O 密集与 CPU 密集这些前置概念讲清楚,再回到线程、进程和协程本身,建立一套足够实用的选型思路。

并发与并行不是一回事

并发强调的是“多个任务在时间上交错推进”,并行强调的是“多个任务在同一时刻真正同时执行”。

这两者经常被混用,但它们解决的问题并不完全一样。

对于很多后端服务和脚本任务来说,我们更关心的是:当一个任务在等待网络、磁盘或外部系统时,能不能让另一个任务继续往前跑。这通常就是并发问题。

所以学并发的第一步,不是选库,而是先认清你的任务到底在被什么阻塞。

多线程:适合哪些任务?

线程适合 I/O 密集型任务,比如:

  • 网络请求;
  • 文件读写;
  • 数据库等待;
  • 调用外部服务。

因为这类场景里,线程大量时间都在等待外部资源,而不是持续占满 CPU。

线程的优势是接入成本相对低,很多阻塞式库也更容易直接配合使用。但线程也有共享内存带来的同步复杂度,所以它不是“开得越多越好”的工具。

多进程:如何绕开 GIL 的限制?

当任务是 CPU 密集型时,比如:

  • 图像处理;
  • 大量数值计算;
  • 批量压缩转换;
  • 某些复杂解析任务;

多进程通常会更合适。

因为每个进程都有独立解释器和内存空间,能够更好利用多核资源。

代价则在于:

  • 创建成本更高;
  • 进程间通信更复杂;
  • 状态共享不像线程那样直接。

所以多进程解决的是“CPU 压力”,但引入的是更重的隔离和协调成本。

协程:面向 I/O 的高并发方案

协程常被称为轻量并发单元,它特别适合高并发 I/O 场景。

和线程不同,协程的切换通常由程序在合适位置显式让出控制权,也就是 await 这类操作点。这意味着它很高效,但也要求整个调用链尽量遵守异步模型。

所以协程的优势不只是“轻”,而是它让大量 I/O 等待型任务能在单线程事件循环里高效调度。

不过,一旦中间混入阻塞式调用,整个异步链条就可能被卡住,这也是它和线程模型很不一样的地方。

线程、进程与协程的选择准则

一个非常实用的判断方式是:

  • 主要在等外部资源,优先考虑线程或协程;
  • 主要在吃 CPU,优先考虑进程;
  • 已有同步阻塞式库很多,线程往往更容易接入;
  • 整个链路都能异步化,而且并发量较高,协程通常更有优势。

所以选型的核心不是“谁更高级”,而是“当前任务的阻塞形态和项目约束是什么”。

常见误区:不是并发越多越好

并发并不是免费午餐。

线程太多会带来上下文切换和共享状态问题;进程太多会吃掉更多内存和调度成本;协程太多也可能把外部资源打满,或者让系统难以定位瓶颈。

所以并发设计真正成熟的标志,不是你会开多少任务,而是你知道什么时候该收敛、限流、隔离和监控。

总结与预告

这一节我们先把 Python 并发世界里的几个核心参与者摆到了同一张图上。会不会并发,不只是会调用某个库,而是能不能先判断任务特征,再选对模型。

下一节我们会继续聚焦协程和异步编程,把 asyncio 的事件循环、任务调度和异常处理真正讲透。