“IO 密集用 asyncio,CPU 密集用 multiprocessing,老项目维护用 threading”——道理都懂,真到选型还是纠结。结合最近写的几个爬虫和数据处理脚本,聊聊我的判断标准。
并发的本质是把“等待”利用起来。判断方法很简单:任务时间花在网卡/磁盘上还是 CPU 上?前者是 IO 密集,后者是 CPU 密集。爬虫、调 API、读写文件都是前者;图像处理、数值计算是后者。
单线程事件循环,几万个并发连接只占一个核,内存开销也小。典型的写法:
import asyncio, aiohttp
async def fetch(session, url):
async with session.get(url) as resp:
return await resp.text()
async def main(urls):
async with aiohttp.ClientSession() as s:
return await asyncio.gather(*(fetch(s, u) for u in urls))
代价是整条链路都得是异步的,混进同步库就堵住事件循环。没有现成异步库时,用 run_in_executor 把同步调用丢进线程池兜底。
老项目、内部 SDK 只有同步接口,硬上 asyncio 反而痛苦。这时线程池完全够用:
from concurrent.futures import ThreadPoolExecutor
with ThreadPoolExecutor(max_workers=32) as pool:
results = list(pool.map(fetch_sync, urls))
注意 GIL 只影响 CPU,IO 等待时线程是真正并行的,几十个线程跑网络任务绰绰有余。
GIL 之下多线程算数学题毫无收益,只能多进程。Python 3.13 之后还有 concurrent.futures.ProcessPoolExecutor 起步成本更低的说法,但进程间传输数据仍是最大开销,能把数据切成大块再传就别小块传。
一句话版:先问自己任务在等 IO 还是烧 CPU → IO 且生态有异步库 → asyncio;IO 但全同步 → 线程池;烧 CPU → 进程池。规模小的时候,随手一个线程池往往是性价比最高的选择,别为了“先进”强行异步。
← 返回首页