How asyncio.sleep isn't blocking thread?

青春壹個敷衍的年華 提交于 2021-02-16 13:41:28

问题


I'm reading 'Fluent Python' by 'Luciano Ramalho' over and over, but I couldn't understand asyncio.sleep's behavior inside asyncio.

Book says at one part:

Never use time.sleep in asyncio coroutines unless you want to block the main thread, therefore freezing the event loop and probably the whole application as well. (...) it should yield from asyncio.sleep(DELAY).

On the other part:

Every Blocking I/O function in the Python standard library releases the GIL (...) The time.sleep() function also releases the GIL.

As time.sleep() releases GIL codes on other thread can run, but blocks current thread. Since asyncio is single-threaded, I understand that time.sleep blocks asyncio loop.

But, how asyncio.sleep() isn't blocking thread? Is it possible to not delay event loop and wait at the same time?


回答1:


The function asyncio.sleep simply registers a future to be called in x seconds while time.sleep suspends the execution for x seconds.

You can test how both behave with this small example and see how asyncio.sleep(1) doesn't actually give you any clue on how long it will "sleep" because it's not what it really does:

import asyncio 
import time
from datetime import datetime


async def sleep_demo():
    print("sleep_demo start: ", datetime.now().time())
    await asyncio.sleep(1)
    print("sleep_demo end: ", datetime.now().time())
    

async def I_block_everyone():
    print("I_block_everyone start: ", datetime.now().time())
    time.sleep(3)
    print("I_block_everyone end: ", datetime.now().time())
    
    
asyncio.gather(*[sleep_demo(), I_block_everyone()])

This prints:

sleep_demo start:  04:46:55.902913
I_block_everyone start:  04:46:55.903119
I_block_everyone end:  04:46:58.905383
sleep_demo end:  04:46:58.906038

The blocking call time.sleep prevent the event loop from scheduling the future that resumes sleep_demo. In the end, it gains control back only after approximately 3 seconds.

Now concerning "The time.sleep() function also releases the GIL.", this is not a contradiction as it will only allow another thread to execute (but the current thread will remain pending for x seconds). Somewhat both look a bit similar, in one case the GIL is released to make room for another thread, in asyncio.sleep, the event loop gains control back to schedule another task.




回答2:


Under the hood, asyncio has an "event loop": it's a function that loops over queue of tasks. When you add new task, it's added in the queue. When task yields, it gets suspended and event loop moves onto next task. Suspended tasks are ignored until they resume. When task finishes, it gets removed from the queue.

For example, when you call asyncio.run, it adds new task into queue and then enters event loop until there are no more tasks.

Few quotes from official documentation:

  • Event Loop

The event loop is the core of every asyncio application. Event loops run asynchronous tasks and callbacks, perform network IO operations, and run subprocesses.

  • Task

Event loops use cooperative scheduling: an event loop runs one Task at a time. While a Task awaits for the completion of a Future, the event loop runs other Tasks, callbacks, or performs IO operations.

When you call asyncio.sleep, it suspends current task, thus allowing other tasks to run. Well, I am basically retelling the documentation:

sleep() always suspends the current task, allowing other tasks to run.



来源:https://stackoverflow.com/questions/62493718/how-asyncio-sleep-isnt-blocking-thread

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!