异步编程本质是“不等”而非“同时跑”,通过显式await挂起协程、交还控制权给事件循环,依赖非阻塞I/O和系统级多路复用(如epoll)实现高并发;误用同步库或CPU密集操作会阻塞整个事件循环,需用aiohttp、run_in_executor等正确方案。

因为异步不是靠“同时跑”,而是靠“不等”——I/O 一卡住,立刻切走,等系统通知再回来。
async/await 不是多线程,而是协作式让点
你写 await asyncio.sleep(1) 或 await response.text(),协程就主动挂起,把控制权交还给事件循环,而不是像 time.sleep(1) 那样死占着线程不动。这个“让点”是显式的、可预测的,没有操作系统介入,也没有锁和上下文切换开销。
常见错误现象:
- 误用同步库(如
requests.get())混进协程里 → 整个事件循环被阻塞,所有并发任务停摆 - 在协程里做密集计算(如大列表推导、正则全量匹配)→ CPU 占满,其他协程拿不到调度机会
正确做法:
立即学习“Python免费学习笔记(深入)”;
- HTTP 请求必须用
aiohttp、httpx.AsyncClient等异步客户端 - CPU 密集操作要包进
loop.run_in_executor()扔到线程池 - 永远避免在协程中调用
time.sleep(),改用await asyncio.sleep()
事件循环 + 非阻塞 I/O 是底层支撑
Python 的 asyncio 默认使用 epoll(Linux)或 kqueue(macOS)这类系统级 I/O 多路复用机制。它不轮询 socket,而是注册监听,等内核说“这个 socket 有数据了”,才去读;没数据时,事件循环立刻转去执行别的协程。
关键参数差异:
-
socket.setblocking(False)是基础:read/write 立即返回,无数据就抛BlockingIOError -
asyncio.open_connection()内部自动完成非阻塞设置和事件注册 - 手动用
select或poll模拟也能做到,但效率低、易出错,asyncio封装了这些细节
性能影响:
- 1 万个连接,只占用约几 MB 内存(每个协程几百字节)
- 线程数维持为 1(或加 1–2 个 executor 线程),避免千级线程上下文切换
协程数量可以轻松过万,但资源瓶颈不在“数量”上
你能启动 10 万个 async def 协程,不代表服务器能扛住 10 万真实请求。真正卡住的往往是外部依赖:
- DNS 查询未加缓存 → 大量协程卡在
getaddrinfo上(建议用aiodns或配置本地 DNS 缓存) - 数据库连接池太小 → 所有协程排队等
acquire(),实际并发被限死(如asyncpg.create_pool(min_size=10, max_size=100)) - 远端服务限流或响应慢 → 协程堆积在
await session.post(),形成背压
所以并发能力不是由 asyncio 单方面决定的,而取决于整个链路中**最慢、最窄的那个环节**是否也异步化、是否配对了合理容量。
最容易被忽略的一点:协程本身不耗资源,但 await 后面的对象如果没正确实现 __await__ 或内部用了阻塞调用,它就不是真异步——这种“伪异步”代码上线后,会在高负载下突然暴露出单点阻塞问题,而且极难复现和定位。


















