asyncio.sleep 如何不阻塞线程?
How asyncio.sleep isn't blocking thread?
我一遍又一遍地阅读 'Luciano Ramalho' 的 'Fluent Python',但我无法理解 asyncio.sleep 在 asyncio 中的行为。
书中有一部分说:
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).
另一边:
Every Blocking I/O function in the Python standard library releases the GIL (...) The time.sleep() function also releases the GIL.
因为 time.sleep() 在其他线程上释放 GIL 代码可以 运行,但会阻塞当前线程。由于 asyncio 是单线程的,我知道 time.sleep 会阻塞 asyncio 循环。
但是,asyncio.sleep() 怎么不阻塞线程呢?是否可以不延迟事件循环,同时等待?
在幕后,asyncio
有一个“事件循环”:它是一个循环任务队列的函数。当您添加新任务时,它会添加到队列中。当任务产生时,它被挂起并且事件循环移动到下一个任务。挂起的任务将被忽略,直到它们恢复。当任务完成时,它会从队列中移除。
例如,当您调用asyncio.run
时,它会将新任务添加到队列中,然后进入事件循环,直到没有更多任务。
官方文档中的一些引述:
The event loop is the core of every asyncio application. Event loops run asynchronous tasks and callbacks, perform network IO operations, and run subprocesses.
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.
当您调用 asyncio.sleep
时,它会暂停当前任务,从而允许其他任务 运行。好吧,我基本上是在复述 documentation:
sleep() always suspends the current task, allowing other tasks to run.
函数 asyncio.sleep
只是 while time.sleep
suspends the execution for x
seconds.
你可以用这个小例子测试两者的行为,看看 asyncio.sleep(1)
实际上并没有给你任何关于它会“休眠”多长时间的线索,因为它不是它真正做的:
import asyncio
import time
from datetime import datetime
async def sleep_demo():
print("async sleep start 1s: ", datetime.now().time())
await asyncio.sleep(1)
print("async sleep end: ", datetime.now().time())
async def I_block_everyone():
print("regular sleep start 3s: ", datetime.now().time())
time.sleep(3)
print("regular sleep end: ", datetime.now().time())
asyncio.gather(*[sleep_demo(), I_block_everyone()])
这会打印:
async sleep start 1s: 04:46:55
regular sleep start 3s: 04:46:55
regular sleep end: 04:46:58
async sleep end: 04:46:58
阻塞调用time.sleep
阻止事件循环调度恢复sleep_demo
的未来。最后,它仅在大约 3 秒后才重新获得控制权(即使我们明确请求 1 秒异步睡眠)。
现在关于“time.sleep()
函数也释放GIL。”,这并不矛盾,因为它只会让另一个线程执行(但当前线程将保持等待x
秒)。两者看起来有点相似,在一种情况下,GIL 被释放以为另一个线程腾出空间,在 asyncio.sleep
中,事件循环获得控制权以安排另一个任务。
我一遍又一遍地阅读 'Luciano Ramalho' 的 'Fluent Python',但我无法理解 asyncio.sleep 在 asyncio 中的行为。
书中有一部分说:
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).
另一边:
Every Blocking I/O function in the Python standard library releases the GIL (...) The time.sleep() function also releases the GIL.
因为 time.sleep() 在其他线程上释放 GIL 代码可以 运行,但会阻塞当前线程。由于 asyncio 是单线程的,我知道 time.sleep 会阻塞 asyncio 循环。
但是,asyncio.sleep() 怎么不阻塞线程呢?是否可以不延迟事件循环,同时等待?
在幕后,asyncio
有一个“事件循环”:它是一个循环任务队列的函数。当您添加新任务时,它会添加到队列中。当任务产生时,它被挂起并且事件循环移动到下一个任务。挂起的任务将被忽略,直到它们恢复。当任务完成时,它会从队列中移除。
例如,当您调用asyncio.run
时,它会将新任务添加到队列中,然后进入事件循环,直到没有更多任务。
官方文档中的一些引述:
The event loop is the core of every asyncio application. Event loops run asynchronous tasks and callbacks, perform network IO operations, and run subprocesses.
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.
当您调用 asyncio.sleep
时,它会暂停当前任务,从而允许其他任务 运行。好吧,我基本上是在复述 documentation:
sleep() always suspends the current task, allowing other tasks to run.
函数 asyncio.sleep
只是 time.sleep
suspends the execution for x
seconds.
你可以用这个小例子测试两者的行为,看看 asyncio.sleep(1)
实际上并没有给你任何关于它会“休眠”多长时间的线索,因为它不是它真正做的:
import asyncio
import time
from datetime import datetime
async def sleep_demo():
print("async sleep start 1s: ", datetime.now().time())
await asyncio.sleep(1)
print("async sleep end: ", datetime.now().time())
async def I_block_everyone():
print("regular sleep start 3s: ", datetime.now().time())
time.sleep(3)
print("regular sleep end: ", datetime.now().time())
asyncio.gather(*[sleep_demo(), I_block_everyone()])
这会打印:
async sleep start 1s: 04:46:55
regular sleep start 3s: 04:46:55
regular sleep end: 04:46:58
async sleep end: 04:46:58
阻塞调用time.sleep
阻止事件循环调度恢复sleep_demo
的未来。最后,它仅在大约 3 秒后才重新获得控制权(即使我们明确请求 1 秒异步睡眠)。
现在关于“time.sleep()
函数也释放GIL。”,这并不矛盾,因为它只会让另一个线程执行(但当前线程将保持等待x
秒)。两者看起来有点相似,在一种情况下,GIL 被释放以为另一个线程腾出空间,在 asyncio.sleep
中,事件循环获得控制权以安排另一个任务。