事件循环被阻塞,90%以上是因为协程里混入了同步阻塞调用(如time.sleep()、requests.get()、sqlite3.execute()),应改用asyncio.sleep()、aiohttp等异步替代方案,或通过run_in_executor/to_thread卸载到线程池。

直接结论:事件循环被阻塞,90%以上是因为协程里混入了同步阻塞调用(如 time.sleep()、requests.get()、socket.recv()、数据库原生驱动的 execute()),而不是 asyncio 本身有问题。
asyncio.run() 启动服务时,哪些写法会悄悄卡死循环
常见错误是把 Web 框架(如 FastAPI、aiohttp)和自定义阻塞逻辑混在同一协程中执行。例如:
- 在 FastAPI 的
@app.get("/data")路由函数里调用subprocess.run(...)或open("large_file.txt").read() - 用
sqlite3.connect().execute(...)替代aiosqlite,哪怕只查一条记录 - 在
async def函数里写time.sleep(1)—— 它不会让出控制权,整个事件循环停 1 秒
这些操作不是“慢”,而是“完全不交还控制权”。只要有一个请求触发这类代码,所有其他并发请求都会排队等待。
阻塞调用必须卸载到线程池,但要注意 executor 配置
正确做法是用 loop.run_in_executor() 或 asyncio.to_thread()(Python 3.9+)把阻塞操作移出事件循环主线程:
import asyncio import time <p>def blocking_db_query(): time.sleep(0.5) # 模拟慢查询 return "result"</p><p>async def api_handler(): loop = asyncio.get_running_loop()</p><h1>✅ 正确:提交到线程池,不阻塞循环</h1><pre class="brush:php;toolbar:false;">result = await loop.run_in_executor(None, blocking_db_query) return result
关键细节:
- 传
None给run_in_executor()会使用默认线程池,但默认池大小是min(32, os.cpu_count() + 4),高并发下可能成为瓶颈 - 生产环境建议显式创建带限流的
ThreadPoolExecutor,比如max_workers=8,避免线程爆炸 -
asyncio.to_thread()更简洁,但底层仍走线程池,不能替代异步驱动
别依赖“看起来是异步”的库,检查底层是否真用非阻塞 I/O
很多库名字带 async,但实际只是包装了线程池(比如某些旧版 aiomysql 封装)。判断标准只有一个:它是否直接调用 socket.setblocking(False) 并注册到事件循环?
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
- ✅ 可信:
aiohttp、httpx.AsyncClient、aiosqlite、asyncpg - ❌ 危险:
requests(无论怎么await都没用)、pymysql、sqlite3、原生socket对象的recv()/send() - ⚠️ 灰色地带:某些 ORM(如
SQLModel)默认同步,需配合asyncpg+ 手动run_in_executor包装
一个快速验证方法:在协程里加一句 await asyncio.sleep(0),如果加了之后阻塞消失,说明原操作根本没让出控制权——就是同步调用。
uvloop 是最简单有效的性能兜底方案
即使你已清理完所有阻塞点,标准 asyncio 事件循环在高并发下仍有调度开销。换用 uvloop 能直接提升吞吐、降低延迟,且几乎零改造成本:
import asyncio import uvloop <p>asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())</p><h1>后续所有 asyncio.run()、get_event_loop() 都自动使用 uvloop</h1><p>asyncio.run(your_app()) </p>
注意两点:
- Windows 上需用
uvloop==0.19.0或更高(兼容 Python 3.11+) - 它不能解决阻塞调用问题,只是让“干净的”事件循环跑得更快——所以必须先做完前面三步
真正容易被忽略的,是开发时本地测试往往压不出阻塞问题:单请求、低并发、SSD 读文件快、本地网络延迟低……等上生产面对真实流量,才暴露调度失衡。上线前务必用 locust 或 hey 模拟 50+ 并发请求,观察响应时间毛刺和 CPU 使用率是否突增。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










