问题
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