gpt4 book ai didi

python - asyncio 的默认调度程序何时公平?

转载 作者:行者123 更新时间:2023-12-04 01:15:40 24 4
gpt4 key购买 nike

我的理解是 asyncio.gather旨在同时运行其参数,并且当协程执行 await 表达式时,它为事件循环提供了安排其他任务的机会。考虑到这一点,我惊讶地看到以下代码段忽略了 asyncio.gather 的输入之一。 .

import asyncio                                                             

async def aprint(s):
print(s)

async def forever(s):
while True:
await aprint(s)

async def main():
await asyncio.gather(forever('a'), forever('b'))

asyncio.run(main())
据我了解,会发生以下事情:
  • asyncio.run(main()) 对事件循环进行任何必要的全局初始化,并安排 main() 执行。
  • main() 调度 asyncio.gather(...) 执行并等待其结果
  • asyncio.gather 调度执行every('a') 和forever('b')
  • 无论哪个先执行,它们都会立即等待 aprint() 并在需要时让调度程序有机会运行另一个协程(例如,如果我们以 'a' 开头,那么我们有机会开始尝试评估 'b',这应该已经被安排执行)。
  • 在输出中,我们将看到一行包含“a”或“b”的行,并且调度程序应该足够公平,以便我们在足够长的时间内至少看到其中的一个。

  • 在实践中,这不是我观察到的。相反,整个程序相当于 while True: print('a') .我发现非常有趣的是,即使是对代码的微小更改似乎也重新引入了公平性。例如,如果我们改为使用以下代码,那么我们会在输出中得到大致相等的 'a' 和 'b' 组合。
    async def forever(s):
    while True:
    await aprint(s)
    await asyncio.sleep(1.)
    验证它似乎与我们在无限循环中花费的时间长短没有任何关系,我发现以下更改也提供了公平性。
    async def forever(s):
    while True:
    await aprint(s)
    await asyncio.sleep(0.)
    有谁知道为什么会发生这种不公平以及如何避免它?我想当有疑问时,我可以主动在任何地方添加一个空的 sleep 语句,并希望这样就足够了,但对我来说为什么原始代码没有按预期运行是非常不明显的。
    以防万一,因为 asyncio 似乎经历了很多 API 更改,我在 Ubuntu 机器上使用 Python 3.8.4 的 vanilla 安装。

    最佳答案

    1. whichever of the those executes first, they immediately await aprint() and give the scheduler the opportunity to run another coroutine if desired

    这部分是一个常见的误解。 Python的 await并不意味着“将控制权交给事件循环”,而是“开始执行可等待的,允许它与我们一起挂起”。是的, 如果 等待的对象选择挂起,当前协程也将挂起,等待它的协程也将挂起,依此类推,一直到事件循环。但是如果等待的对象 没有 选择暂停,就像 aprint 一样,等待它的协程也不会。这偶尔是错误的来源,如 herehere .

    Does anyone know why this unfairness might happen and how to avoid it?


    幸运的是,这种效果在不真正与外界交流的玩具示例中最为明显。虽然你可以通过添加 await asyncio.sleep(0) 来修复它们到战略地点(甚至记录在案以强制进行上下文切换),您可能 shouldn't在生产代码中做到这一点。
    一个真正的程序将依赖于来自外部世界的输入,无论是来自网络、本地数据库还是来自另一个线程或进程填充的工作队列的数据。实际数据很少会以如此快的速度到达程序的其余部分,如果确实如此,饥饿可能是暂时的,因为程序最终会因输出端的背压而暂停。在极少数情况下,程序从一个源接收数据的速度比处理它的速度快,但仍然需要观察来自另一个源的数据,您可能会遇到饥饿问题,但如果有的话,可以通过强制上下文切换来解决这个问题显示发生。 (我还没有听说有人在生产中遇到它。)
    除了上面提到的错误之外,更经常发生的是协程调用占用大量 CPU 的代码或遗留的阻塞代码,这最终会占用事件循环。这种情况应该通过将 CPU/blocking 部分传递给 run_in_executor 来处理。 .

    关于python - asyncio 的默认调度程序何时公平?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63455683/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com