如果我有两个功能在做
async with mylock.acquire():
....
一旦释放了锁,是否可以保证等待的第一个赢,还是选择的顺序不同? (例如,随机,任意,最新等)
我要问的原因是,如果不是先来先服务,那么很容易出现饥饿的情况,其中尝试获取锁的第一个函数永远不会赢得它。
答案 0 :(得分:2)
当我们谈论某种事物如何工作时,重要的是要区分规范中表示的保证和实现的副作用。第一个不应更改(至少在主要版本内),第二个可以在将来随时更改。
Martijn的答案清楚地表明,当前的实现可以保持秩序。那么对未来的保证呢?
官方文档for Python 3.6提供了保证:
当release()调用将状态重置为解锁时,只有一个协程进行。 在acquire()中被阻止的第一个协程正在处理。
有趣的是,文档for Python 3.7和文档for Python 3.8 dev都没有此行,但是不确定是否是故意的。但是,可以保证github has上的类的文档字符串。
还值得一提的是,threading.Lock
(异步锁的原型)明确表示未定义顺序:
当release()调用将状态重置为解锁时,只有一个线程继续运行; 哪个等待线程继续进行未定义,并且在不同的实现中可能会有所不同。
长话短说,目前只有班级的文档字符串保证维护秩序。还要公平地指出,锁的实现不太可能在不久的将来更改。
但是,请想象有人会对其进行更改(例如,以提高性能)。 docstring是否足以防止以未定义的顺序实现锁定?由您决定。
如果您的代码严重依赖于保留顺序并预期具有较长的生命周期,那么创建自己的锁(子)类将明确保证顺序(OrderedLock
或其他)就可以了。您可以将当前的实施方式卖掉。
如果情况比较简单,您可以选择不理会它并使用当前的实现。
答案 1 :(得分:1)
是的,正在等待锁的任务被添加到队列中,并以FIFO为基础被唤醒。
特别是,当尝试获取锁定的锁时,会创建一个future,该信号等待锁已经可用的信号,称为 waiter 。该服务员被添加到collections.deque()
双端队列created in Lock.__init__()
self._waiters = collections.deque()
当当前持有锁的任务释放锁时,Lock._wake_up_first()
method被称为:
def _wake_up_first(self):
"""Wake up the first waiter if it isn't done."""
try:
fut = next(iter(self._waiters))
except StopIteration:
return
# .done() necessarily means that a waiter will wake up later on and
# either take the lock, or, if it was cancelled and lock wasn't
# taken already, will hit this again and wake up a new waiter.
if not fut.done():
fut.set_result(True)
Future.set_result()
call标志着未来已经完成。究竟如何导致等待将来重新获得控制权的任务取决于实现,但通常是通过给事件循环提供回调函数来尽早实现的。
Lock.acquire()
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
因此,如果锁已锁定,则通过创建一个将来对象(使其添加到_waiters
队列中)来使当前任务等待,并等待将来。这将阻塞任务,直到将来有结果为止(await fut
直到那时都不会返回)。事件循环不会给此任务任何处理时间。
当前持有该锁并释放该锁的另一个任务将导致_waiters
队列中的第一个(等待时间最长)的未来具有结果集,从而间接导致正在等待该未来的任务再次变为活动状态。当释放锁的任务将控制权移交给事件循环(等待其他事件)时,事件循环将控制权移交给等待该未来的任务,未来将返回到await fut
行,未来将被删除。从队列中获得,然后将锁授予在该将来等待的任务。
这里有一种Lock.acquire()
方法显式处理的竞争情况:
将不会赋予任务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
队列中具有活跃期货的解锁锁实际上被认为是锁定的。