【问题标题】:1-item asyncio queue - is this some standard thing?1-item asyncio queue - 这是一些标准的东西吗?
【发布时间】:2021-02-23 08:13:03
【问题描述】:

在我的一个 asyncio 项目中,我经常使用一种同步方法,并且想知道它是否是某种标准工具,我可以给 google 提供一个名称以了解更多信息。我使用术语“1-item queue”只是因为我没有更好的名字。这是一个降级队列,与Queue(maxsize=1) 无关。

#  [controller] ---- commands ---> [worker]

控制器向worker发送命令(queue.put,实际上是put_nowait),worker等待它们(queue.get)并执行它们,但特殊规则是唯一的最后一个命令是重要且立即的替换之前所有未完成的命令。因此,队列中等待执行的命令永远不会超过 1 个。

为了实现这一点,控制器在放置之前清除队列。没有queue.clear,所以它必须丢弃(和get_nowait)等待的项目,如果有的话。 (queue.clear 的缺席让我产生了这个疑问。)

在工作人员方面,如果命令执行需要休眠,则将其替换为带有超时的newcmd=queue.get。当超时发生时,它是睡眠;当get 成功时,当前工作中止并开始执行newcmd

【问题讨论】:

  • 听起来队列不是您的用例的原语,尽管您以某种方式使其工作。我会用一对asyncio.Event 来实现这种同步,我会在上面附加你需要运行的命令。这样你就不需要清除任何东西,只需分配新的命令就可以了。
  • @user4815162342 你能写更多关于这对事件的细节吗?对不起,我没明白你的意思。
  • 抱歉,“对”是编辑留下的,指的是(事件,命令)对,命令是您现在放入队列的对象。后来我想到你可以将命令附加到事件中(使用普通的赋值,event.my_command = ...,所以你不需要随身携带两个物体。这有帮助吗?
  • @user4815162342 让我们看看。控制器设置my_command attr 并调用event.set。工作人员 - 当被唤醒时 - 获取命令并调用 event.clear。是的,我认为这应该有效。 Event 是一个更简单的概念,但不解决数据交换,必须附加命令。 OTOH a Queue 传输数据,但必须以不寻常的方式使用。不知道我是否应该改变我的程序,但你的想法帮助我从另一个角度看待这个问题。谢谢。
  • 正确,这正是我的想法。您实际上并不需要一个队列,只需要一个唤醒机制和一个用于将单个对象附加到唤醒的单元(最新命令)。 Event 是 asyncio 中最简单的多用途唤醒设备(也有条件,但它增加了您根本不需要的多线程的复杂性)。如果你使用事件,不要忘记在event.wait() 之后立即从工作人员调用event.clear(),否则事件将保持设置。不要担心竞争条件,因为协程无法在等待之间切换。

标签: python python-asyncio computer-science


【解决方案1】:

您使用的队列类型不是标准的 - 有一次性队列之类的东西,但它完全不同。

队列并不真正适合您的用例,尽管您付出了一些努力。您实际上并不需要任何类型的排队,您需要一个容纳单个对象(可以替换)的插槽和一个唤醒机制。 asyncio.Event 可用于唤醒,您可以将有效负载对象(命令)附加到事件的属性。例如:

async def worker(evt):
    while True:
        await evt.wait()
        evt.clear()
        if evt.last_command is None:
            continue
        last_command = evt.last_command
        evt.last_command = None
        # execute last_command, possibly with timeout
        print(last_command)

async def main():
    evt = asyncio.Event()
    workers = [asyncio.create_task(worker(evt)) for _ in range(5)]
    for i in itertools.count():
        await asyncio.sleep(1)
        evt.last_command = f"foo {i}"
        evt.set()

asyncio.run(main())

这与基于队列的方法之间的一个区别是,设置事件将唤醒所有工作人员(如果有多个工作人员),即使第一个工作人员立即调用evt.clear()。另一方面,队列项将保证被移交给queue.get() 的单个等待者。

【讨论】:

  • 请注意,此代码严重依赖于 asyncio 的协作性而非抢占性 - mainworker 的关键部分都受到以下事实的保护,即在临界区。这在没有显式锁的线程中是行不通的。
  • @user2357112supportsMonica 正确 - 但这是 asyncio 编程的典型特征,并非真正特定于此代码。例如,await evt.wait() 后跟 evt.clear() 对于异步事件来说是惯用的,并且对于多线程事件会失败。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-05
相关资源
最近更新 更多