asyncio通过事件循环实现单线程内非阻塞i/o调度,避免线程空转与上下文切换开销,在i/o密集场景下显著提升吞吐量和cpu利用率。

因为 asyncio 让单线程在等待 I/O 时立刻切走,而不是干等——它不靠多线程抢资源,靠的是“不浪费每一毫秒空闲时间”。
为什么阻塞式 I/O 是吞吐量的硬伤
同步代码里调用 requests.get() 或 conn.execute(),线程就卡住不动了,CPU 空转,连接也占着不放。100 个并发请求 = 100 个线程在排队等响应,内存涨、上下文切换猛、系统调用多。
而异步不是“更快地执行”,是“更少地闲置”。只要底层驱动(如 aiomysql、aiohttp)用了非阻塞 socket 和 IO 多路复用(epoll/kqueue),一次系统调用就能同时监听成百上千个连接的状态变化。
- 同步模型下,每个请求独占一个线程,平均 CPU 利用率常低于 15%
- 异步模型下,单线程可维持 10K+ 并发连接,CPU 利用率稳定在 60–80%
- 关键不是“快”,是“不把时间花在等上”——等数据库返回的 200ms 里,能干完另外 3 个 API 调用
await 不是“暂停函数”,是“交出控制权给事件循环”
await 的本质不是让当前协程睡一会儿,而是告诉 asyncio.get_event_loop():“我这会儿要等 I/O,你先去跑别的”。事件循环立刻查表,挑一个已就绪(比如网络响应已到、文件已读完)的协程 resume。
这个切换发生在用户态,开销约 1μs;线程切换要进内核,动辄 3–5μs。1000 次切换下来,差出几毫秒,对高并发就是几百 QPS 的差距。
-
await asyncio.sleep(0)是手动让出控制权的最小代价方式,常用于避免长协程饿死其他任务 - 如果 await 的对象不是真正的异步原语(比如误 await 了一个普通函数或
time.sleep()),协程就假异步——还是阻塞的 -
async def定义的函数返回的是协程对象,不 await 就不会执行,也不会挂起
真正起作用的是整个异步生态链,不是单个 async/await
光写 async def 没用。必须整条链路都非阻塞:HTTP 客户端得用 aiohttp,数据库得用 aiomysql 或 asyncpg,文件操作得用 asyncio.to_thread() 包装或 aiofiles,连日志写入都最好避开阻塞式 logging.FileHandler。
常见断点:
- 混用
requests+asyncio:直接退化为同步,整个 event loop 被卡死 - 数据库连接没设
autocommit=True或没正确 close():连接池耗尽,后续协程全堵在connect() - 忘了
await某个协程调用(比如漏了await cursor.execute(...)):返回的是协程对象而非结果,后续逻辑崩,但错误可能延迟到await其他地方才暴露
吞吐量提升不是线性的,取决于 I/O 等待占比
如果你的协程里有大量 CPU 密集计算(比如解析大 JSON、做图像缩放),asyncio 帮不上忙——它只优化 I/O 等待。这时候该上 concurrent.futures.ProcessPoolExecutor。
实测中,当单次请求中 I/O 等待时间占总耗时 70% 以上时,异步改造的吞吐收益最明显;若计算占比高,盲目加 async 反而因事件循环调度引入额外开销。
最容易被忽略的一点:event loop 本身是单线程的。它再高效,也无法并行执行两个 CPU 绑定任务。所谓“高并发”,仅针对 I/O 等待密集的场景成立。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











