并发编程总结
约 1701 字大约 6 分钟
2026-05-10
阅读导览
并发编程总结
这一部分学习了 Python 中常见的并发方式:多线程、多进程、线程池、进程池和 asyncio。
- 核心概念回顾
- 技术方案对比
- 选择方案速查
- 常见坑
实践清单
学习练习清单
- 1按任务类型选型:IO 等待 → 线程池/asyncio;CPU 计算 → 进程池;高并发网络 → asyncio。
- 2走一遍学习路线:threading → 线程安全 → Queue → 线程池 → 多进程 → asyncio → GIL → 实践。
- 3项目里把“并发数量上限”作为必填项,无论用什么模型都不要无脑放大。
- 4并发代码自检:异常是否拿出来了?是否有超时?退出时资源是否关闭?日志里能看出哪个任务挂了?
- 5卡住、慢、错的现象先降并发到 1~2,加日志再排查 —— 比直接看 traceback 高效得多。
这一部分学习了 Python 中常见的并发方式:多线程、多进程、线程池、进程池和 asyncio。
核心概念回顾
| 概念 | 含义 |
|---|---|
| 并发 | 多个任务在同一段时间内交替推进 |
| 并行 | 多个任务在同一时刻真正同时执行 |
| 同步 | 调用后等待结果返回 |
| 异步 | 发起任务后可以先做别的事 |
| 阻塞 | 当前执行流被卡住 |
| 非阻塞 | 操作不能立即完成时也不阻塞当前执行流 |
| CPU 密集型 | 主要消耗 CPU 计算能力 |
| IO 密集型 | 主要等待外部资源 |
技术方案对比
| 方案 | 模块 | 优点 | 缺点 | 适合场景 |
|---|---|---|---|---|
| 多线程 | threading | 简单、共享内存方便 | 线程安全问题、受 GIL 影响 | 简单 IO 并发 |
| 多进程 | multiprocessing | 可利用多核、绕过 GIL | 启动成本高、通信复杂 | CPU 密集型 |
| 线程池 | ThreadPoolExecutor | 易用、适合批量任务 | 仍需注意线程安全 | 批量 IO 任务 |
| 进程池 | ProcessPoolExecutor | 易用、可并行计算 | 参数和结果需序列化 | 批量 CPU 任务 |
| asyncio | asyncio | 高并发、低开销 | 心智模型复杂、不能阻塞事件循环 | 高并发网络 IO |
选择方案速查
- 爬取少量网页:线程池
- 爬取大量网页:asyncio
- 批量读取文件:线程池
- 图片压缩处理:进程池
- WebSocket 服务:asyncio
- 数据库批量查询:线程池或异步数据库驱动
- 大量数学计算:进程池、NumPy 或专门计算库
- 简单后台任务:线程
常见坑
- CPU 密集型任务使用多线程,结果没有变快。
- asyncio 协程中调用
time.sleep(),阻塞整个事件循环。 - 多线程共享变量但没有加锁。
- 使用锁后在锁里做耗时网络请求。
- 启动大量线程或任务,没有限制并发数量。
- 进程代码没有写
if __name__ == '__main__':。 - 只提交任务,不处理异常。
- 没有超时,任务卡住后程序无法退出。
- 忘记调用
queue.task_done(),导致join()永远等待。 - 为了并发写出过度复杂的代码。
学习路线建议
- 理解基础概念:并发、并行、同步、异步、阻塞、非阻塞。
- 学会多线程:启动、等待、传参、daemon。
- 学会线程安全:锁、死锁、减少共享状态。
- 学会队列:生产者消费者、哨兵值、优雅退出。
- 学会多进程:进程隔离、进程通信、main 保护。
- 学会线程池和进程池:submit、map、Future、as_completed。
- 学会 asyncio:协程、事件循环、Task、gather、取消、超时。
- 理解 GIL:知道什么时候线程有用,什么时候该用进程。
最后的建议
并发编程不是目的,而是手段。写并发代码前,先问自己:
- 当前程序真的慢吗?
- 慢在哪里?
- 是 CPU 瓶颈还是 IO 瓶颈?
- 并发后如何限制资源?
- 出错时如何恢复?
- 程序如何优雅退出?
能回答这些问题,再选择并发方案,代码会稳很多。
一张学习路线图
并发编程建议按这个顺序学习:
并发基本概念
↓
threading:理解线程、join、daemon
↓
线程安全:理解共享状态、Lock、Queue
↓
ThreadPoolExecutor:用池管理任务
↓
multiprocessing / ProcessPoolExecutor:处理 CPU 密集任务
↓
asyncio:理解 async、await、Task、Queue、Semaphore
↓
GIL、最佳实践、调试与排错不要跳过线程安全和 Queue。它们是理解并发 bug 的关键。
任务类型到方案选择
| 任务 | 推荐起点 | 说明 |
|---|---|---|
| 同时请求多个接口 | ThreadPoolExecutor | 复用同步 requests 最简单 |
| 大量异步 HTTP 请求 | asyncio + httpx | 适合异步生态 |
| CPU 密集计算 | ProcessPoolExecutor | 绕开 GIL,注意数据传输成本 |
| 线程之间传任务 | queue.Queue | 比共享列表更安全 |
| 协程之间传任务 | asyncio.Queue | 异步生产者消费者 |
| 控制请求并发 | Semaphore | 防止请求过猛 |
| 简单后台任务 | threading.Thread | 数量少时可以直接用 |
初学者不要纠结“哪种最高级”,先选自己能写对、能排错的方案。
并发代码自检清单
写完并发代码后,可以检查:
- 是否真的需要并发,同步版是否已经正确?
- 并发数量有没有上限?
- 每个任务失败时会怎样?
- 主程序是否等待所有任务完成?
- 是否存在多个任务同时修改共享变量?
- 是否有超时,避免无限等待?
- 日志能否看出哪个任务失败?
- 程序退出时资源是否关闭?
这张清单比背很多 API 更实用。
常见报错和现象
| 现象 | 常见原因 |
|---|---|
| 程序卡住不退出 | 忘记 join、队列没 task_done、任务一直阻塞 |
| 结果数量不对 | 竞态条件、共享变量未加锁 |
| 异步代码没执行 | 只创建了协程,没有 await 或 asyncio.run |
| 并发后更慢 | 并发过高、任务太小、瓶颈不在等待 |
| CPU 没跑满 | 线程受 GIL 影响,或任务本身不是 CPU 密集 |
| 错误看不见 | Future/Task 的异常没有取出 |
遇到问题时,先把并发数量降到 1~2,再加日志观察执行顺序。
总结
Python 提供了多种并发工具,每种工具都有适合的场景。线程适合 IO,多进程适合 CPU,asyncio 适合高并发网络 IO;线程池和进程池适合多数批量任务。掌握它们的边界,比记住 API 更重要。
重点总结
要点回收
- 并发学习路线是概念、线程、线程安全、池、多进程、asyncio、GIL 和实践排错。
- 选择方案先看任务类型:IO 等待、CPU 计算、共享状态、任务数量和库生态。
- 并发代码要检查上限、异常、等待、共享状态、超时、日志和资源关闭。
版权所有
版权归属:Shuo Liu
