【问题标题】:When will/won't Python suspend execution of a coroutine?Python 何时会/不会暂停协程的执行?
【发布时间】:2019-10-05 19:50:12
【问题描述】:

当我在 cpython 3.6 上运行它时,以下程序会打印一次 hello world,然后永远旋转。

作为旁注,取消注释await asyncio.sleep(0) 行会导致它每秒打印hello world,这是可以理解的。

import asyncio

async def do_nothing():
    # await asyncio.sleep(0)
    pass

async def hog_the_event_loop():
    while True:
        await do_nothing()

async def timer_print():
    while True:
        print("hello world")
        await asyncio.sleep(1)

loop = asyncio.get_event_loop()
loop.create_task(timer_print())
loop.create_task(hog_the_event_loop())
loop.run_forever()

这种行为(打印hello world 一次)对我来说很有意义,因为hog_the_event_loop 从不阻塞,因此不需要暂停执行。 我可以依赖这种行为吗?await do_nothing() 行运行时,是否有可能不是进入do_nothing() 协程,而是执行实际上会暂停和恢复timer_print(),从而导致程序打印hello world 第二次?

更笼统地说:python 什么时候会暂停执行协程并切换到另一个协程?是否有可能任何使用await 关键字?还是仅在导致底层select 调用(例如I/O、睡眠定时器等)的情况下?

补充说明

我知道如果hog_the_event_loop 看起来像这样,它肯定不会让另一个协程执行:

async def hog_the_event_loop():
    while True:
        pass

我试图具体解决await do_nothing() 是否与上述不同的问题。

【问题讨论】:

  • 如果没有await 行,您的hog_the_event_loop 在无限循环中只有同步代码。这将阻止事件循环。每次迭代都会到达await,事件循环可以离开协同处理并执行其他等待任务。
  • 谢谢。只是为了确保我理解:在await do_nothing() 中仅使用await 不符合异步代码的条件,并且不足以像await asyncio.sleep(0) 那样导致另一个任务执行?
  • 必须有一些可以等待的东西。
  • 我记得 David Beazley 的一些上下文(但细节很模糊,所以我会留下评论而不是回答):async/await 模型是协作多任务处理的一个示例:一个函数以一种方式实现,以在将控制权交还给事件循环的适当或有用时向函数执行中的点发出信号;该函数使用await 发送该信号。可以说,没有await 的函数不是“合作”的。
  • 关于您的跟进,await do_nothing() 确立了do_nothing() 也将参与合作机制的期望。因为它没有,hog_the_event_loop() 中的无限循环永远不会放弃控制。这至少是我对此的直观理解;自从我花了很多时间以来已经有一段时间了。

标签: python python-3.x async-await python-asyncio coroutine


【解决方案1】:

这种行为(打印 hello world 一次)对我来说很有意义,因为hog_the_event_loop 从不阻塞,因此不需要暂停执行。 我可以依赖这种行为吗?

是的。这种行为直接源于语言如何指定和实现await。将其更改为挂起而不使等待对象本身挂起肯定是一个重大更改,对于基于 async/await 的 asyncio and other Python 库。

更笼统地说:python 什么时候会暂停执行协程并切换到另一个协程?是否有可能使用 await 关键字?

从调用者的角度来看,任何await 都可以潜在地挂起,由等待的对象决定,也称为awaitable。因此,特定await 是否会挂起的最终决定是在可等待对象上(或者在可等待对象上,如果它是协程则等待自身,等等)。等待不选择挂起的 awaitable 与运行普通 Python 代码相同 - 它不会将控制权传递给事件循环。

实际挂起的叶子等待对象通常与 IO 就绪或超时事件有关,但并非总是如此。例如,如果队列为空,等待 queue 项将暂停,等待 run_in_executor 将暂停,直到在另一个线程中运行的函数完成。

值得一提的是,asyncio.sleepexplicitly guaranteed 暂停执行并推迟到事件循环,即使指定的延迟为 0(在这种情况下,它将在下一个事件循环通过时立即恢复)。

【讨论】:

    【解决方案2】:

    不,await do_nothing() 永远不会挂起。 await 传播通过暂停周围的协程作为响应来暂停等待。但是当 awaitable 已经准备好时,没有什么可等待的,并且从 await 继续执行(通常带有返回值)。另一种思考“无需等待”的方式是,事件循环实际上没有对象可以作为从名义暂停恢复时间的基础;甚至“一旦其他挂起的任务暂停就恢复”是一个必须表示为某个对象的计划(例如sleep(0))。

    一个什么都不做的协程总是准备好的,就像一个休眠的协程在时间过去后准备好一样。换句话说,一个只休眠 N 次的协程会挂起 N 次——即使 N 为 0。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-04-18
      • 1970-01-01
      相关资源
      最近更新 更多