Python并发编程与异步入门:线程、进程、线程池与 asyncio 基础
写 Python 程序时,很多任务看起来都是“按顺序一行一行执行”的。
这当然是默认方式,但真实开发中,你常常会遇到这样的需求:
- 同时请求多个接口;
- 同时处理多个文件;
- 后台跑任务,前台继续响应;
- 提高 I/O 密集任务效率;
- 利用多核处理 CPU 密集任务。
这时就会接触到一个很重要的话题:并发编程。
不过“并发”“并行”“异步”这些词,初学者常常容易混在一起。
所以这一篇不会一开始就堆很多概念,而是尽量围绕实际问题来讲清楚:
- 什么情况下需要并发;
- 线程、进程和异步各自适合什么;
- Python 中最常见的并发工具有哪些;
asyncio到底解决什么问题。
理解这一章之后,你就能对 Python 中常见的并发方案形成比较清晰的整体认识。
一、为什么需要并发?
假设你要请求 100 个网页。
如果你这样写:
1 | for url in urls: |
程序通常会:
- 请求第 1 个;
- 等它返回;
- 再请求第 2 个;
- 再等它返回;
- 如此反复。
这种方式当然能工作,但如果每次都要等网络响应,整体会很慢。
再比如:
- 你要同时下载多个文件;
- 你要同时监听多个连接;
- 你要批量处理很多 I/O 操作;
如果全都串行执行,资源利用率往往不高。
所以并发的核心目标之一是:
当某个任务在等待时,让其他任务先继续做。
二、并发、并行、异步到底有什么区别?
这是最容易混淆的一部分。
2.1 并发(concurrency)
并发更强调:
- 多个任务在一段时间内“交替推进”;
- 宏观看起来像是同时进行。
不一定真的同一时刻都在运行,但整体上多个任务都在往前走。
2.2 并行(parallelism)
并行更强调:
- 多个任务真的在同一时刻同时执行;
- 通常需要多核 CPU 或多个执行单元。
2.3 异步(asynchronous)
异步是一种组织任务的方式,核心思想是:
- 当任务需要等待时,不阻塞整个流程;
- 而是先把控制权交出去,让别的任务运行。
所以可以简单理解为:
- 并发是现象;
- 并行是更强的同时执行;
- 异步是一种实现并发的手段。
入门阶段你不需要把定义抠得特别死,先理解“它们解决的是让多个任务更高效推进的问题”就够了。
三、线程:适合很多 I/O 密集任务
3.1 什么是线程?
你可以先把线程理解为:
一个进程内部的执行单元。
一个程序启动后通常至少有一个主线程,而你也可以创建更多线程来并发执行任务。
3.2 一个最简单的线程示例
1 | import threading |
这里:
start()启动线程;join()等待线程执行完成。
3.3 线程适合什么场景?
线程通常比较适合:
- 网络请求;
- 文件读写;
- 日志收集;
- 爬虫中的并发抓取;
- 其他 I/O 密集型任务。
因为这类任务很多时间都在“等待”,线程能帮助你把等待时间利用起来。
四、线程安全与锁:多个线程一起改数据怎么办?
线程虽然方便,但它也带来一个问题:
多个线程如果同时修改同一份数据,可能会发生竞争。
4.1 一个共享变量示意
1 | import threading |
你可能以为结果一定是 200000,但在真实并发环境下,共享数据修改往往需要额外保护。
4.2 使用锁 Lock
1 | import threading |
锁的意义可以简单理解为:
- 同一时刻只允许一个线程进入关键区域;
- 避免共享数据被同时乱改。
4.3 锁的使用建议
- 共享可变数据时要想到锁;
- 锁范围不要过大;
- 不要滥用,否则会降低并发收益。
五、线程池:比手动管理线程更实用
真实项目里,很多时候不会手动一个个创建线程,而是用线程池。
5.1 为什么线程池更常用?
因为它能帮你:
- 复用线程;
- 控制最大并发数量;
- 更方便收集执行结果;
- 写法更清晰。
5.2 使用 ThreadPoolExecutor
1 | from concurrent.futures import ThreadPoolExecutor |
5.3 提交任务并获取结果
1 | from concurrent.futures import ThreadPoolExecutor |
线程池在很多实际场景里都非常适合,例如批量下载、并发请求接口等。
六、进程:更适合 CPU 密集型任务
6.1 什么是进程?
进程可以先理解为:
操作系统分配资源和运行程序的基本单位。
和线程相比,不同进程之间通常内存空间隔离更明显。
6.2 一个简单示例
1 | from multiprocessing import Process |
6.3 为什么进程常用于 CPU 密集任务?
例如:
- 大量数值计算;
- 图像处理;
- 数据压缩;
- 复杂算法运算。
这类任务的瓶颈主要在 CPU,而不是 I/O 等待。
这时使用多进程通常更有意义。
七、进程池:批量 CPU 任务更常见的写法
7.1 使用 ProcessPoolExecutor
1 | from concurrent.futures import ProcessPoolExecutor |
7.2 什么时候优先想到进程池?
- 任务之间相对独立;
- 主要瓶颈在 CPU;
- 需要利用多核能力;
- 不想手动管理多个子进程。
八、线程和进程该怎么选?
这是最常见的问题之一。
你可以先用一个非常实用的判断方法:
8.1 I/O 密集型任务
例如:
- 网络请求;
- 文件读写;
- 数据库访问;
- 等待外部服务响应。
这类任务通常优先考虑:
- 线程;
- 线程池;
- 异步 I/O。
8.2 CPU 密集型任务
例如:
- 图像计算;
- 大规模数学运算;
- 编码压缩;
- 复杂数据转换。
这类任务通常优先考虑:
- 多进程;
- 进程池。
你不需要死记教条,但这个判断在多数情况下很实用。
九、asyncio:另一种并发思路
线程和进程都很好理解,因为它们比较接近“开更多执行单元”。
而 asyncio 则更像是:
在一个线程里,通过协作式调度,让大量 I/O 任务高效推进。
9.1 async 和 await 是什么?
你可以先把它理解为:
async def定义协程函数;await表示在这里等待某个异步结果,同时让出执行权。
9.2 一个最简单的示例
1 | import asyncio |
这段代码仍然是顺序等待。
9.3 并发运行多个协程:gather()
1 | import asyncio |
这时两个任务会并发推进,总时间通常接近 1 秒,而不是 2 秒。
9.4 asyncio 适合什么?
它特别适合:
- 大量网络请求;
- 高并发 I/O;
- Web 爬虫;
- 聊天、消息、连接类任务;
- 异步 Web 框架。
十、为什么说 asyncio 更适合 I/O,而不是 CPU?
因为异步的强项在于:
- 当任务需要等待时,把时间让给其他任务;
- 避免线程切换的额外成本;
- 在大量 I/O 请求时非常高效。
但如果任务本身一直在疯狂计算 CPU,没有等待点,那么 await 也帮不了你太多。
所以可以记住:
- 高等待 → 异步很有优势;
- 高计算 → 进程通常更合适。
十一、一个非常实用的选择思路
很多人学完之后最需要的其实不是“定义”,而是一个快速判断框架。
可以先这么用:
11.1 普通任务
- 先写串行版本;
- 如果性能足够,就不要为了并发而并发。
11.2 主要在等 I/O
- 先考虑线程池;
- 并发量更大时,再考虑异步。
11.3 主要在吃 CPU
- 优先考虑进程池。
11.4 有大量网络请求,且希望更高吞吐
- 往往可以进一步考虑
asyncio+ 异步 HTTP 客户端。
十二、一个综合示例:线程池和异步的对比思路
假设你要处理很多网页请求。
线程池版思路:
1 | from concurrent.futures import ThreadPoolExecutor |
异步版思路(这里只展示结构感):
1 | import asyncio |
这两个方向都能实现并发,但适用工具链和复杂度不同。
十三、学习并发时的常见误区
13.1 不要一开始就把所有方案混在一起
很多初学者一上来就同时学:
- 线程;
- 进程;
- 异步;
- 协程;
- 锁;
- 队列;
- 回调。
结果很容易越学越乱。
更好的顺序通常是:
- 先理解“为什么需要并发”;
- 再理解线程和进程差别;
- 最后再学
asyncio。
13.2 不要为了“高级”而强行异步
如果你的任务规模很小,串行就够快,那没必要一上来就异步化。
13.3 不要忽视共享数据问题
只要有多个执行单元一起修改同一份状态,就要想到线程安全或进程间通信问题。
13.4 不要把并发当成性能银弹
并发不是所有问题的答案。
有时真正慢的原因可能是:
- SQL 查询没优化;
- 算法复杂度太高;
- 接口本身很慢;
- 数据结构选错了。
十四、学习建议:这一章最值得优先掌握什么?
14.1 先建立“任务类型判断”能力
也就是学会区分:
- 我这是 I/O 密集型任务,还是 CPU 密集型任务?
这比死记 API 更重要。
14.2 先掌握线程池和进程池
因为它们在真实项目里非常常见,而且比手写底层线程/进程管理更实用。
14.3 asyncio 先理解核心思想即可
先理解:
async defawaitasyncio.run()asyncio.gather()
这就已经足够入门。
14.4 以后再往更深处走
后续如果继续深入,通常还会接触:
- 线程安全队列;
- 生产者消费者模型;
- 异步 HTTP 客户端;
- 异步 Web 框架;
- 任务调度系统。
十五、总结
这一篇我们系统梳理了 Python 中“并发编程与异步入门”的核心内容:
- 为什么需要并发:理解等待型任务和批量任务带来的效率问题;
- 线程:适合理解和处理很多 I/O 密集场景;
- 锁:用于保护共享数据,避免线程竞争;
- 线程池:更常见、更实用的线程管理方式;
- 进程:更适合 CPU 密集型任务;
- 进程池:用于更方便地管理多进程任务;
asyncio:一种特别适合 I/O 并发的异步模型;- 任务选择思路:学会区分 I/O 密集和 CPU 密集,并选择合适方案。
如果说前面的章节更多是在处理“单个任务怎么做”,那么这一篇更像是在回答:
当任务变多、等待变多、规模变大时,Python 应该如何更高效地组织执行流程?
掌握这一篇之后,你对 Python 的工程能力会再上一个台阶。
继续往后写时,比较自然的衔接方向通常有两个:
- Python项目结构、打包与发布入门:让代码更规范、更可复用;
- Python Web 开发或异步网络实战:把并发能力真正用于工程项目。