使用time.sleep自定义my_sleep,asyncio.gather为何未实现预期并发?
我尝试自定义协程函数my_sleep,内部调用time.sleep而非asyncio.sleep(),用asyncio.gather执行my_sleep(4)和my_sleep(7)时,预期总耗时7秒,但实际用了约11秒;而用线程实现类似逻辑时,总耗时符合预期的7秒。请问asyncio.gather创建的Task对象为何无法像线程那样将time.sleep识别为IO绑定操作?
代码示例:
import time import asyncio async def my_sleep(delay): time.sleep(delay) async def main(): start = time.time() await asyncio.gather(my_sleep(4), my_sleep(7)) print("Took", time.time()-start, "seconds") asyncio.run(main()) # Expected: Took 7 seconds # Got: Took 11.011508464813232 seconds
原因解释
asyncio是单线程协作式调度模型
asyncio的事件循环运行在单个线程中,所有Task的切换完全依赖于当前Task主动让出CPU控制权。只有当Task执行到await一个可等待对象(比如asyncio.sleep、异步IO操作)时,才会把控制权交还给事件循环,让其他Task有机会运行。time.sleep是同步阻塞调用,会卡住整个线程
time.sleep(delay)是一个同步阻塞函数,它会让整个线程进入休眠状态,期间事件循环完全无法调度其他Task。你的my_sleep协程调用time.sleep时,事件循环线程被死死卡住,必须等这个sleep执行完,才会去处理另一个my_sleep任务,所以总耗时是4+7=11秒。asyncio.sleep才是异步友好的休眠方式
asyncio.sleep(delay)是专门为异步框架设计的可等待对象,当你await它时,当前Task会主动把控制权交还给事件循环,事件循环可以立刻调度其他就绪的Task。这样两个休眠任务就能“并行”执行,总耗时取两个延迟的最大值7秒。线程是抢占式调度模型
线程由操作系统负责调度,采用抢占式策略:当一个线程调用time.sleep进入休眠时,操作系统会主动把CPU时间片分配给其他线程,所以两个线程的sleep操作是真正并行的,总耗时自然是较长的那个7秒。
内容的提问来源于stack exchange,提问作者AK-CHP

