asyncio 不适用于 CPU 密集任务,仅在高并发 I/O 场景(如 HTTP 请求、WebSocket)中有效;需用异步库、避免阻塞调用、复用连接池、启用超时与调试模式。

asyncio 不是给 CPU 密集任务用的
如果你的任务主要是计算(比如矩阵乘法、加密解密、大量循环处理数据),asyncio 不仅不会提速,反而因事件循环开销拖慢整体性能。它的价值只在 I/O 等待时间长、并发请求多的场景——比如同时调用几十个 HTTP 接口、读写大量小文件、处理高并发 WebSocket 连接。
常见错误现象:asyncio.run() 包裹纯 time.sleep() 或 sum(range(10**7)),结果比同步还慢;或者把 requests.get() 直接丢进 async def 里,报错或阻塞整个事件循环。
- 必须用真正支持异步的库:HTTP 用
aiohttp或httpx.AsyncClient,数据库用asyncpg、aiomysql,文件操作尽量用aiofiles - 同步函数不能直接 await,要用
loop.run_in_executor()包装(但别滥用,线程池调度也有成本) - Python 3.11+ 的
asyncio.to_thread()更简洁,适合偶尔调用单个阻塞函数
别让一个 slow coroutine 拖垮整个 event loop
asyncio 是单线程协作式调度,一个协程里写了死循环、长时间计算或没设 timeout 的 I/O,其他所有任务就卡住不动——这不是“并发失败”,而是你亲手堵死了调度器的路。
典型表现:压测时吞吐量上不去,asyncio.wait_for() 频繁抛 TimeoutError,日志里看到大量任务堆积在 pending 状态。
立即学习“Python免费学习笔记(深入)”;
- 所有外部 I/O 调用必须加超时:
await asyncio.wait_for(task, timeout=5) - 避免在协程里做复杂解析或正则匹配;大文本处理前先判断长度,过长就用
to_thread - 用
asyncio.create_task()显式启动子任务,别全堆在await asyncio.gather()里——后者失败一个全崩,且无法单独 cancel
连接池和 session 复用比写 async 更关键
很多人花半天改出 async def fetch(),吞吐却只涨 10%,原因往往是没管底层连接复用。每次 aiohttp.ClientSession() 新建又关闭,TCP 握手、TLS 协商、连接释放全重来,I/O 时间根本没省下来。
真实瓶颈常在连接管理,不在 Python 层协程调度。
- 全局复用一个
aiohttp.ClientSession实例(用lifespan或模块级变量),别在每次请求里async with aiohttp.ClientSession() as s: - 设置合理连接池参数:
connector = aiohttp.TCPConnector(limit=100, limit_per_host=30),避免端口耗尽或服务端限流 - HTTP/2 支持要看服务端是否开启;
httpx.AsyncClient(http2=True)比 aiohttp 在某些场景更稳,但调试难度略高
监控和压测时别只看 CPU 和 QPS
CPU 使用率低、QPS 却上不去?大概率是事件循环被阻塞,或协程在等 I/O 但没暴露出来。asyncio 自带的 asyncio.debug 模式和第三方工具如 aiomonitor 才能定位真问题。
容易忽略的点:DNS 解析默认是同步阻塞的,aiohttp 会卡住;Python 3.12 开始才原生支持异步 DNS,之前得靠 aiodns 替换 resolver。
- 启用 debug 模式:
asyncio.run(main(), debug=True),看 warning 是否提示 “Executingsleeptook X seconds” - 用
asyncio.all_tasks()+task.get_coro()抓当前挂起的协程栈,快速识别卡点 - 压测工具本身要是异步的(比如
locust配HttpUser+aiohttp),否则客户端就成了新瓶颈


















