并发概论:选型与决策
这是并发编程系列的总览篇。读完这篇文章,你会知道"我这任务该用什么方案"。后续每篇文章解决一个具体武器的使用细节。
1. 核心概念辨析
| 概念 | 一句话 | 生活类比 |
|---|---|---|
| 并发(Concurrency) | 多个任务交替执行,看起来同时 | 一个人在两个菜锅间来回翻炒 |
| 并行(Parallelism) | 多个任务真正同时执行 | 两个人各炒一个锅 |
| 同步(Synchronous) | 调用后阻塞等待结果再继续 | 点餐后站在柜台等,饭好了才走 |
| 异步(Asynchronous) | 调用后立即返回,结果通过回调或 await 获取 | 点餐后拿号坐下,好了叫号 |
| 阻塞(Blocking) | 当前线程停止等待某个操作完成 | 水龙头开着,你站在那儿等水烧开 |
| 非阻塞(Non-blocking) | 操作未就绪时立即返回,不等待 | 水龙头开着,你先去切菜 |
2. I/O 密集 vs CPU 密集
这是决定选型的关键分类。
2.1 什么是 I/O 密集
大部分时间在等——等网络响应、等磁盘读写、等数据库返回。
常见例子:
- HTTP 请求 / 爬虫
- 文件读写(尤其是大文件)
- 数据库查询
- 调用外部 API(LLM、支付网关)
特点:CPU 大部分时间空闲,瓶颈在网络或磁盘延迟。
2.2 什么是 CPU 密集
大部分时间在算——数学计算、压缩、加密、图像处理、机器学习推理。
常见例子:
- 图片压缩 / 缩放
- 数据分析(pandas 聚合)
- 加密解密(hash、AES)
- 科学计算(矩阵运算)
特点:CPU 一直跑满,瓶颈在计算能力。
2.3 怎么判断
经验法:把任务跑一遍,看 CPU 占用率——
- 接近 100%:CPU 密集
- 远低于 100%:I/O 密集
直觉法:代码里有 sleep、网络请求、文件读写?大概率 I/O 密集。全是数学运算和循环?大概率 CPU 密集。
3. GIL:Python 多线程的"原罪"
3.1 GIL 是什么
GIL(Global Interpreter Lock)——全局解释器锁。CPython 解释器内部的一把锁,同一时刻只允许一个线程执行 Python 字节码。
3.2 为什么存在
Python 的内存管理(引用计数、垃圾回收)不是线程安全的。GIL 是最简单的解决方案:用一把大锁保护整个解释器,避免并发修改导致内存错误。
3.3 影响是什么
| 场景 | GIL 的影响 |
|---|---|
| CPU 密集 + 多线程 | 多个线程抢 GIL,反而可能比单线程更慢 |
| I/O 密集 + 多线程 | 线程在等 I/O 时自动释放 GIL,其他线程能跑,有用 |
3.4 记忆口诀
I/O 密集 → 多线程或异步(GIL 不是问题,因为等 I/O 时会释放)
CPU 密集 → 多进程(绕开 GIL,每个进程有独立解释器)
Python 3.13 开始有实验性的"无 GIL"版本(PEP 703),但生产代码 5 年内还是按上面规则选。
4. Python 三种并发模型
| 模型 | 模块 | 适合场景 | 切换开销 | 最大并发量 |
|---|---|---|---|---|
| 多线程 | threading | I/O 密集(少量任务) | 低(KB 级内存) | 数千 |
| 多进程 | multiprocessing | CPU 密集 | 高(MB 级内存) | CPU 核心数 |
| 异步 | asyncio | I/O 密集(高并发) | 极低(用户态切换) | 数万 |
补充:concurrent.futures
Python 3.2 引入的统一接口,封装了多线程和多进程:
# 多线程
from concurrent.futures import ThreadPoolExecutor
# 多进程
from concurrent.futures import ProcessPoolExecutor
# 两者 API 完全一致——改一个类名就能切换方案
这是现代 Python 的首选并发 API,后续文章会重点讲解。
5. 选型决策树
这是本文的核心,也是整个系列的地图。
你的任务是 I/O 密集还是 CPU 密集?
│
├─ I/O 密集
│ ├─ 任务数少(< 100)→ ThreadPoolExecutor
│ └─ 任务数多(> 100)→ asyncio + 异步库
│
├─ CPU 密集
│ └─ ProcessPoolExecutor
│
└─ 调用外部命令(pandoc / ffmpeg / git)
└─ subprocess.run
更细化的决策:
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 几个 API 调用 | ThreadPoolExecutor | 简单,够用 |
| 几百个爬虫请求 | asyncio + aiohttp | 高并发,低开销 |
| 图片批量压缩 | ProcessPoolExecutor | CPU 密集,需要多核 |
| 调用 pandoc / ffmpeg | subprocess.run | 外部命令,标准接口 |
| 混合任务(先拉数据再处理) | asyncio + run_in_executor | 异步拉数据,线程池处理 |
6. 和现有内容的关系
| 文件 | 定位 | 关系 |
|---|---|---|
| Python 进阶/并发与异步 | API 速查手册 | 保留,不修改。新文章侧重原理讲解和实战 |
| 本系列(00-05) | 原理 + 实战教程 | 从概念到代码,每个武器一篇 |
如果你已经读过 并发与异步.md,那篇是"查 API"用的;这个系列是"理解原理 + 学会选型"用的。互为补充。
7. 各篇导航
| 篇目 | 内容 | 适合你想学的 |
|---|---|---|
| python-multithreading | threading + Lock + ThreadPoolExecutor | "我想用多线程但不知道怎么用" |
| python-multiprocessing | multiprocessing + IPC + ProcessPoolExecutor | "CPU 密集任务怎么加速" |
| python-asyncio | 协程 + 事件循环 + 异步生态 | "我想学 asyncio" |
| python-subprocess | subprocess.run 工业级模板 | "怎么从 Python 调外部命令" |
| python-concurrent-llm-calls | 综合案例,贯穿所有知识点 | "给我一个完整的实战例子" |