asyncio是单线程内通过协程让出控制权实现I/O并发的机制,适用于HTTP请求等等待型任务,对CPU密集型无效;须用真正异步库(如aiohttp),避免阻塞调用。

asyncio 不是多线程,也不是多进程
它是在单线程里靠「让出控制权」来并发处理多个 I/O 任务的机制。你写 await 的地方,不是暂停整个程序,而是告诉事件循环:“我这儿要等网络/磁盘响应,先别管我,去跑别的协程”。CPU 不再空转,但你也别指望它加速 sum(range(10**8)) 这种纯计算——asyncio 对 CPU 密集型任务无效,甚至更慢。
- 常见错误现象:
async def cpu_heavy(): return sum(range(10**8))+await cpu_heavy()→ 实际比同步还慢,因为协程切换白花钱 - 适用场景:HTTP 请求、数据库查询、文件读写、WebSocket 通信等明显卡在“等响应”的环节
- 性能影响:1000 个并发请求,同步可能耗时 1000 秒(串行),asyncio 通常压到 3–5 秒(并行等待)
必须用 await 调用真正异步的函数
不是所有带 async 前缀的函数都“真异步”;也不是所有耗时操作加了 async 就自动变快。关键看底层是否接入事件循环——比如 requests.get() 是同步阻塞的,哪怕包在 async def 里,也会拖垮整个事件循环。
- 正确做法:用
aiohttp替代requests,用aiomysql/asyncpg替代pymysql/psycopg2 - 容易踩的坑:
time.sleep(1)会锁死事件循环,必须换成await asyncio.sleep(1) - 参数差异:
aiohttp.ClientSession()需显式async with,漏掉会导致连接泄漏;asyncio.gather()并发执行多个协程,而asyncio.wait()更适合带超时或条件控制的场景
asyncio.run() 是入口,但别在库代码里调用它
这个函数会新建一个事件循环、运行协程、然后彻底关闭循环。对脚本没问题,但在已有事件循环的环境(如 FastAPI、Trio、Jupyter 或某些测试框架)中重复调用,会报 RuntimeError: asyncio.run() cannot be called from a running event loop。
- 使用场景:独立脚本、命令行工具、简单 demo
- 替代方案:库函数应只返回协程对象,由上层决定如何调度;生产服务中常直接用
loop.run_until_complete(main())或交给框架管理 - 兼容性影响:Python 3.7+ 才有
asyncio.run();低于此版本需手动获取/启动循环
并发不等于并行,gather 和 create_task 行为不同
asyncio.gather(a(), b(), c()) 是“等全部完成才返回”,适合无依赖的批量请求;而 asyncio.create_task() 立即把协程提交进事件循环队列,适合需要中途检查状态、取消、或按需 await 的情况。
立即学习“Python免费学习笔记(深入)”;
- 常见错误现象:用
gather包裹有副作用的协程(如写日志、改全局变量),结果顺序不可控,且无法提前中断 - 实操建议:若某任务可能超时或失败,优先用
create_task+asyncio.wait_for()+task.cancel()组合 - 性能差异:大量任务用
gather会一次性创建所有协程对象,内存占用略高;create_task可配合生成器或分批提交,更可控
真正难的不是写 async def,而是判断哪些 IO 库支持异步、哪些调用链不能混入同步阻塞、以及怎么在不破坏现有结构的前提下渐进改造。这些细节没踩过两次坑,很难凭文档猜出来。











