【问题标题】:Does the lock in asyncio.Condition have other purpose besides compatibility with threading.Condition?asyncio.Condition 中的锁除了与 threading.Condition 兼容还有其他用途吗?
【发布时间】:2018-07-25 10:30:43
【问题描述】:

我想问一下asyncio.Condition。我不熟悉这个概念,但我从学生时代就知道并理解锁、信号量和队列。

我找不到很好的解释或典型用例,只有this example。我看了看源码。核心功能是通过期货 FIFO 实现的。每个等待的协程都会添加一个新的 future 并等待它。另一个协程可能会调用notify(),它会设置来自 FIFO 的一个或多个未来的结果,并唤醒相同数量的等待协程。到目前为止真的很简单。

但是,实现和使用比这更复杂。等待的协程必须首先获取与条件关联的锁才能等待(wait() 在等待时释放它)。此外,通知者必须获得一个锁才能通知()。这导致在每个操作之前with 声明:

async with condition:
    # condition operation (wait or notify)

否则RuntimeError 发生。

我不明白拥有这个锁的意义。我们需要用锁保护什么资源?在 asyncio 中,事件循环中可能总是只有一个协程执行,没有线程已知的“关键部分”。

这个锁是真的需要(为什么?)还是只是为了与线程代码兼容?

我的第一个想法是为了兼容性,但在这种情况下,为什么他们不删除锁而保留使用?即制作

async with condition:

基本上是一个可选的无操作。

【问题讨论】:

  • 如果你解除锁定,你有一个事件,而不是一个条件。
  • 在 asyncio 中,事件循环中可能总是只有一个协程在执行,没有线程已知的“关键部分”。这仅在关键时成立部分没有await。确实,对于线程,您必须锁定一切(或使用原子指令),但在 asyncio 中,如果有可能暂停,您仍然必须锁定,只要存在await,就会出现这种情况。
  • @MartijnPieters 我明天会研究你的完整答案,但我想对你的评论做出反应。有区别:Event: - 如果在await wait() 之前调用set(),则等待立即返回。 Condition:如果在await.wait() 之前调用notify(),则等待阻塞。我认为锁不参与其中,这就是为什么没有锁的条件与事件不同。
  • @VPfB:这当然涉及到锁,因为没有锁你不能等待或通知。
  • @VPfB:是的,事件和条件有所不同,我在简化。如果您要使用事件和锁实现条件原语之类的东西,则必须确保在等待之前清除事件,并在每次wait() 返回时清除事件。如果您不需要锁定,那么您可能正在寻找事件而不是条件。

标签: python python-asyncio


【解决方案1】:

这个答案与threading.Condition vs threading.Event基本相同;条件没有锁是一个事件,而不是一个条件(*)

条件用于表示资源可用。等待条件的任何人都可以使用该资源,直到他们完成它。为确保没有其他人可以使用该资源,您需要锁定该资源:

resource = get_some_resource()

async with resource.condition:
    await resource.condition.wait()
    # this resource is mine, no-one will touch it
    await resource.do_something_async()

# lock released, resource is available again for the next user

注意wait() 恢复后锁没有被释放!在锁被释放之前,没有其他等待相同条件的协程可以继续,通过锁对资源的访问是独占的。注意锁是在等待的时候释放的,所以其他协程可以自己加入队列,但是wait()要最终返回锁必须先重新获取。

如果您不需要协调对共享资源的访问,请使用事件;条件基本上是将锁和事件组合成一个原语,避免常见的实现陷阱。

请注意,多个条件可以共享锁。这将让您发出特定阶段的信号,而其他协程可以等待该特定阶段的到来。共享锁将协调对单个资源的访问,但在启动每个阶段时会发出不同的信号。

对于线程,提供条件的典型用例是单个生产者,多个消费者都在等待生产者处理的项目。工作队列是共享资源,生产者获取条件锁将一个项目推入队列,然后调用notify(),此时等待条件的下一个消费者获得锁(因为它从wait()返回) 并且可以从队列中删除该项目以进行处理。这并不能完全转化为基于协程的应用程序,因为协程没有线程系统所具有的闲置等待工作待完成问题,因此启动消费者协程要容易得多根据需要执行例程(可能使用信号量来设置上限)。

也许更好的例子是aioimaplib library,它完全支持 IMAP4 事务。这些事务是异步的,但您需要有权访问共享连接资源。因此库使用单个 Condition 对象和wait_for() 来等待特定状态到达,从而为等待该事务状态的协程提供独占连接访问权限。


(*):事件的用例与条件不同,因此其行为与没有锁定的条件略有不同。设置后,需要明确清除事件,而条件在使用时会“自动清除”,并且在没有人等待条件时永远不会“设置”。但是,如果您想在任务之间发出信号并且不需要控制对共享资源的访问,那么您可能需要一个事件。

【讨论】:

  • 谢谢@MartinjPieters。好的用法示例很好地解释了我的问题。现在我看到文档中的描述 “条件变量允许一个或多个协程等待,直到另一个协程通知它们。” 并不是全部。事件和条件(有或“没有”锁)之间的相似性在我看来仍然不是那么大,但没关系。
  • @VPfB:条件和事件是一个常见的比较,因为大多数人对条件锁定要求感到困惑,实际上只是想要事件;例如从一个任务(或线程)向另一个任务(或线程)发出信号,表明某事已经发生。条件是这样的特殊事件,与共享资源相关联。 “我想使用这个资源,但只有当我等待的事情发生时”听起来很像一个事件,不是吗?
猜你喜欢
  • 2019-10-14
  • 1970-01-01
  • 2010-10-25
  • 2014-04-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-05-04
  • 1970-01-01
相关资源
最近更新 更多