文章
asyncio、多线程(threading)以多进程(multiprocessing)如何选择
目录
- 下面先按 What(是什么)、Why(为什么用)、How(怎么用) 三个维度,分别深入讲解 asyncio、多线程 和 多进程,最后再给出一个整合对比,帮助你快速选型。
- 1. asyncio
- What — 是什么
- Why — 为什么用
- How — 怎么用
- 4. 控制并发量:用 asyncio.Semaphore、asyncio.Queue 来限制同时进行的协程数。 5. 超时与取消:asyncio.wait_for()、task.cancel()。
- 2. 多线程(threading)
- What — 是什么
- Why — 为什么用
- How — 怎么用
- 3. 锁与同步:threading.Lock、RLock、Event、Queue 等,避免竞态和死锁。 4. 注意 GIL:线程在执行纯 Python 计算时依旧受全局解释器锁(GIL)限制,无法并行加速 CPU 任务。
- 3. 多进程(multiprocessing)
- What — 是什么
- Why — 为什么用
- How — 怎么用
- 3. 进程间通信:Queue、Pipe、Manager、shared_memory;需要序列化和反序列化,通信比线程/协程慢。 4. 避免大对象传输:尽量在子进程中读取本地数据或通过共享内存,减少跨进程传输开销。
- 四、整合对比
- 选型建议
下面先按 What(是什么)、Why(为什么用)、How(怎么用) 三个维度,分别深入讲解 asyncio、多线程 和 多进程,最后再给出一个整合对比,帮助你快速选型。#
1. asyncio#
What — 是什么#
- Python 标准库提供的异步 IO 框架,核心是一个事件循环(event loop),通过 协程(coroutine) 在单线程中实现高并发、低开销的任务切换。
- 协程是一种“用户态”轻量级线程,不需要操作系统调度,切换成本极低。
Why — 为什么用#
- IO 密集型场景:当你需要并发发起大量网络请求、数据库查询、文件读写等耗时 IO 操作时,使用
asyncio可以极大地提高吞吐。 - 资源消耗低:相比数百、上千线程,协程在内存和上下文切换上更加高效。
- 单进程内的并发:能用单个进程、单个线程完成数千乃至上万的并发连接。
How — 怎么用#
- 定义协程:用
async def
async def fetch(url): # aiohttp.AsyncClient()、asyncio.open_connection() 等异步 API ...
```
2. 启动事件循环: ```python import asyncio asyncio.run(main()) # Python 3.7+
```
3. 创建并调度任务: ```python task = asyncio.create_task(fetch(url)) await asyncio.gather(*tasks)
```
4. 控制并发量:用 asyncio.Semaphore、asyncio.Queue 来限制同时进行的协程数。
5. 超时与取消:asyncio.wait_for()、task.cancel()。#
2. 多线程(threading)#
What — 是什么#
- 基于操作系统线程(OS thread),每个线程有自己的调用栈,但共享同一进程的地址空间(全局变量、堆)。
- 依赖操作系统内核调度,上下文切换成本较高。
Why — 为什么用#
- IO 绑定场景:Python 的 IO 操作(网络、文件、数据库)会释放 GIL,这时线程可以并发执行,提升吞吐。
- 第三方库同步接口:如果要并发调用只能提供同步 API 的库(如某些老旧数据库驱动、系统命令),多线程是最简单的方案。
- 简单并发:几十、上百线程场景下,线程池(
ThreadPoolExecutor)足够好用。
How — 怎么用#
- 手动启动线程:
from threading import Thread
def worker(arg): ... t = Thread(target=worker, args=(…,)) t.start()
```
2. 线程池(推荐): ```python from concurrent.futures import ThreadPoolExecutor
with ThreadPoolExecutor(max_workers=20) as pool: futures = [pool.submit(task, arg) for arg in args] for f in futures: result = f.result()
```
3. 锁与同步:threading.Lock、RLock、Event、Queue 等,避免竞态和死锁。
4. 注意 GIL:线程在执行纯 Python 计算时依旧受全局解释器锁(GIL)限制,无法并行加速 CPU 任务。#
3. 多进程(multiprocessing)#
What — 是什么#
- 基于操作系统进程,每个进程拥有独立的地址空间(独立 GIL、堆、全局变量),通过 IPC(管道、队列、共享内存)通信。
- 进程切换、创建和通信开销最高,但能充分利用多核 CPU。
Why — 为什么用#
- CPU 密集型场景:需要并行执行大量计算(数据处理、科学计算、图像渲染)以突破 GIL 限制。
- 隔离性需求:不同进程崩溃不会互相干扰,适合需要高可靠性的任务。
- 利用多核:每个子进程都有自己的 Python 解释器实例和 GIL,可并行运行。
How — 怎么用#
- 手动创建进程:
from multiprocessing import Process
def worker(arg): ... p = Process(target=worker, args=(…,)) p.start() p.join()
```
2. 进程池(推荐): ```python from multiprocessing import Pool
with Pool(processes=4) as pool: results = pool.map(func, iterable)
```
3. 进程间通信:Queue、Pipe、Manager、shared_memory;需要序列化和反序列化,通信比线程/协程慢。
4. 避免大对象传输:尽量在子进程中读取本地数据或通过共享内存,减少跨进程传输开销。#
四、整合对比#
| 维度 | asyncio | 多线程 | 多进程 |
| **What** | 协程+事件循环,单线程内用户态切换 | 操作系统线程,内核态切换 | 操作系统进程,独立内存空间+独立 GIL |
| **Why** | 大量 IO 并发,高吞吐,低内存/切换成本 | 异步调用仅提供同步接口的库;中等规模 IO 并发;简单易用 | CPU 密集任务并行;多核利用;高隔离性 |
| **How** | `async def`/`await`/`asyncio.run`/任务调度 | `threading.Thread` 或 `ThreadPoolExecutor` | `multiprocessing.Process` 或 `Pool` |
| **GIL** | 只在纯 Python 代码时受限,IO 可释放 | 纯 Python 计算仍受限,IO 可释放 | 每进程一个 GIL,不互相影响 |
| **开销** | 最轻量(上下文切换 µs 级) | 较重(上下文切换 ms 级) | 最重(进程切换 ms+ ;创建和通信成本高) |
| **通信** | 共享全局;无需序列化 | 共享全局;无需序列化 | 需 IPC / 序列化 |
| **推荐场景** | HTTP 客户端/爬虫、高并发 Web 服务、异步数据库驱动等 | 第三方同步库并发调用、中等量 IO 并发、小规模任务并行 | 大规模计算、机器学习训练、科学计算、图像处理等 CPU 密集型 |
选型建议#
- 纯 IO 密集、高并发 → asyncio
- IO+少量计算、已有同步接口 → 多线程
- CPU 密集、需要跨多核并行 → 多进程 通过上面 “What、Why、How” 的拆解以及对比表,希望能帮助你更清晰地理解它们的差异,并在项目中选用最合适的并发模型。