Python的asyncio锁定.获取维持秩序?

2024-06-08 23:24:06 发布

您现在位置:Python中文网/ 问答频道 /正文

如果我有两个功能

async with mylock.acquire():
    ....

一旦解锁,是保证第一个等待的人获胜,还是订单选择不同?(如随机、任意、最新等)

我要问的原因是,如果不是先到先得,那么很容易出现饥饿的情况,第一个试图获取锁的函数永远不会赢得它。在


Tags: 函数订单功能asyncwith情况原因mylock
2条回答

是的,等待锁的任务被添加到队列中,并在FIFO的基础上被唤醒。在

具体地说,当试图获取一个锁定的锁时,会创建一个future来等待锁可用的信号,称为waiter。此服务员被添加到collections.deque()双端队列created in ^{}

self._waiters = collections.deque()

当当前持有锁的任务释放锁时,^{} method被调用:

^{pr2}$

^{} call标志着未来已经完成。这究竟如何导致等待未来重新获得控制权的任务取决于实现,但通常这是通过给事件循环的回调函数来完成的,以便尽早调用。在

^{} method负责添加和删除期货(因为当有信号显示结果已设置时,未来将返回):

fut = self._loop.create_future()
self._waiters.append(fut)

# Finally block should be called before the CancelledError
# handling as we don't want CancelledError to call
# _wake_up_first() and attempt to wake up itself.
try:
    try:
        await fut
    finally:
        self._waiters.remove(fut)
except futures.CancelledError:
    if not self._locked:
        self._wake_up_first()
    raise

因此,如果锁被锁定,则通过创建一个future对象使当前任务等待,该对象被添加到_waiters队列中,并且等待未来。这将阻止任务,直到将来有结果为止(await fut在此之前不会返回)。事件循环不会给此任务任何处理时间。在

当前持有锁并释放锁的另一个任务将导致来自_waiters队列的第一个(最长等待时间)未来具有结果集,间接导致等待该未来的任务再次变为活动状态。当释放锁的任务将控制权交还给事件循环(当等待其他任务时),事件循环将控制权交给等待该未来的任务,未来返回await fut行,未来从队列中移除,并将锁授予等待该未来的任务。在

这里有一个竞争条件情况,Lock.acquire()方法显式处理:

  1. 任务A释放锁,队列为任务B保留等待锁的未来。未来注定要完成。在
  2. 事件循环将控制权交给第三个任务C,该任务正在等待未检索的内容,但现在它又处于活动状态,并且该任务运行试图获取锁的代码。在

但是,任务C不会被授予锁,因为在Lock.acquire()方法的顶部是这个测试:

if not self._locked and all(w.cancelled() for w in self._waiters):
    self._locked = True
    return True

not self._locked在他的例子中是正确的,因为任务A已经释放了它。但是all(w.cancelled() for w in self._waiters)不是,因为任务B在队列中有一个活动的、未取消的未来。所以任务C是为了将他们自己的服务生未来添加到队列中。在_waiters队列中具有活动期货的未锁定锁实际上被视为已锁定。在

当我们谈论某些东西是如何工作的时候,区分规范中表达的保证和实现的副作用是很重要的。第一个不应该更改(至少在主要版本中),第二个可以在将来的任何时候更改。在

Martijn的回答清楚地表明,当前的实现保持了秩序。对未来的保证呢?在

官方文件for Python 3.6提供保证:

only one coroutine proceeds when a release() call resets the state to unlocked; first coroutine which is blocked in acquire() is being processed.

有趣的是,无论是文档for Python 3.7还是文档for Python 3.8 dev都没有这一行,但不确定它是否是有意的。但是,类在github上的docstring has保证。在

还值得一提的是,threading.Lock(asyncio锁的原型)明确表示顺序是未定义的:

only one thread proceeds when a release() call resets the state to unlocked; which one of the waiting threads proceeds is not defined, and may vary across implementations.


长话短说,现在只有类的docstring承诺维持秩序。同样值得注意的是,锁的实现在最近的将来不太可能被更改。在

但是想象一下有人会改变它(例如,为了提高性能)。docstring是否足以阻止使用未定义的顺序实现锁?由你来决定。在

如果您的代码严重依赖于维护顺序,并且预期具有较长的生命周期,那么如果您创建自己的锁(子)类,它将显式地保证顺序(OrderedLock)或其他东西,那没什么不好的。您可以只对当前实现进行供应商化。在

如果情况比较简单,您可以选择不麻烦它,而使用当前的实现。在

相关问题 更多 >