time.sleep() 和事件循环(尤其是 asyncio)最大的冲突点:
time.sleep() 会阻塞整个线程。
而事件循环本质上:
单线程 + 协作式调度
所以一旦你在 async 环境里用了 time.sleep():
整个事件循环都会“卡死”。
这是 Python 异步编程里最经典的坑之一。
1. 最典型错误
很多人会这样写:
import asyncio
import time
async def task():
print("开始")
time.sleep(3)
print("结束")
asyncio.run(task())
看起来没问题。
但这里:
time.sleep(3)
会直接阻塞整个事件循环线程。
2. 为什么会冲突
asyncio 的核心机制:
事件循环不断切换协程
例如:
await asyncio.sleep(1)
并不是“真的睡眠”。
而是:
告诉事件循环:
“我现在可以暂停,
你去执行别的协程吧。”
这叫:
协作式让出控制权
但:
time.sleep()
不会让出控制权。
它会:
直接冻结整个线程
于是:
所有协程暂停
IO 停止
定时器停止
websocket 卡住
心跳断开
3. 最明显的现象
看这个例子。
错误写法:
import asyncio
import time
async def a():
while True:
print("A")
time.sleep(1)
async def b():
while True:
print("B")
await asyncio.sleep(1)
async def main():
await asyncio.gather(a(), b())
asyncio.run(main())
你会发现:
A
A
A
A
只有 A。
因为:
time.sleep()
把整个事件循环堵死了。
4. 正确写法
应该:
await asyncio.sleep()
例如:
import asyncio
async def a():
while True:
print("A")
await asyncio.sleep(1)
async def b():
while True:
print("B")
await asyncio.sleep(1)
asyncio.run(asyncio.gather(a(), b()))
输出:
A
B
A
B
协程真正并发运行。
5. GUI 程序里尤其严重
例如:
Tkinter
PyQt
wxPython
都有自己的事件循环。
如果:
time.sleep(5)
UI 会直接:
假死
无响应
白屏
因为 GUI 事件循环也被堵塞了。
6. FastAPI / aiohttp 最大雷区
很多新手:
在 async 接口里:
@app.get("/")
async def test():
time.sleep(5)
return {"ok": True}
结果:
整个服务器吞吐量暴跌。
因为:
一个请求 sleep:
整个 worker 卡住。
7. 为什么“少量 sleep”也危险
很多人以为:
time.sleep(0.1)
很短没问题。
实际上:
事件循环需要:
高频切换
IO polling
心跳维护
哪怕:
sleep(0.1)
频繁出现。
都会导致:
websocket timeout
延迟暴涨
async queue 堵塞
8. asyncio.sleep(0) 的特殊意义
这个非常经典。
await asyncio.sleep(0)
不是休眠。
而是:
主动让出事件循环控制权
类似:
yield
经常用于:
长循环中避免卡死
主动调度
给别的协程运行机会
例如:
for i in range(1000000):
do_work()
if i % 1000 == 0:
await asyncio.sleep(0)
9. CPU 密集型任务的问题
即使不用:
time.sleep()
也可能卡事件循环。
例如:
async def task():
while True:
pass
因为:
async 不是抢占式。
如果协程不:
await
事件循环永远切不出去。
10. 正确处理阻塞代码
如果必须调用阻塞函数:
应该丢进线程池。
例如:
import asyncio
import time
def blocking():
time.sleep(5)
return "done"
async def main():
result = await asyncio.to_thread(blocking)
print(result)
asyncio.run(main())
这样:
sleep 在线程里
事件循环不被阻塞
11. 旧版本写法
Python 3.9 前常用:
loop.run_in_executor()
例如:
await loop.run_in_executor(None, blocking_func)
本质:
线程池。
12. 第三方库经常偷偷阻塞
很多库:
表面 async。
内部其实:
requests
sqlite
selenium
time.sleep
导致:
“伪异步”。
例如:
async def fake_async():
requests.get(url)
这仍然会阻塞。
13. 检测事件循环被阻塞
asyncio debug 模式:
asyncio.run(main(), debug=True)
或者:
loop.slow_callback_duration = 0.1
可以发现:
Executing
14. uvloop 下更明显
很多高性能框架:
uvicorn
FastAPI
uvloop
事件循环效率极高。
因此:
任何:
time.sleep()
都会更明显地暴露卡顿。
因为:
你把高性能异步循环瞬间变成单线程阻塞程序。
15. 最隐蔽的坑
有些第三方库:
内部:
retry:
time.sleep(1)
即使你外层:
async def
仍然会卡死事件循环。
这也是:
很多“异步程序偶尔卡顿”的根源。
总结
time.sleep() 的本质:
阻塞线程
而 asyncio:
依赖事件循环不断切换协程
所以:
time.sleep()
在 async 环境里会:
冻结整个事件循环
所有协程停摆
IO 停止
网络超时
UI 卡死
正确原则:
异步代码里永远优先:
await asyncio.sleep()
阻塞任务:
asyncio.to_thread()
或者线程池/进程池。