有人可以举例说明何时以及如何使用Twisted DeferredLock。
我有一个DeferredQueue,我想我想要防止竞争条件,但我不确定如何将两者结合起来。
答案 0 :(得分:17)
如果您的关键部分是异步的并且需要保护其不被重叠(可能会说“并发”)执行,请使用DeferredLock
。
以下是此类异步关键部分的示例:
class NetworkCounter(object):
def __init__(self):
self._count = 0
def next(self):
self._count += 1
recording = self._record(self._count)
def recorded(ignored):
return self._count
recording.addCallback(recorded)
return recording
def _record(self, value):
return http.GET(
b"http://example.com/record-count?value=%d" % (value,))
了解next
方法的两个并发使用将如何产生“损坏”结果:
from __future__ import print_function
counter = NetworkCounter()
d1 = counter.next()
d2 = counter.next()
d1.addCallback(print, "d1")
d2.addCallback(print, "d2")
给出结果:
2 d1
2 d2
这是因为对NetworkCounter.next
的第二次调用在第一次调用该方法之前就开始使用_count
属性来生成结果。这两个操作共享单个属性并因此产生不正确的输出。
使用DeferredLock
实例将通过阻止第二个操作开始直到第一个操作完成来解决此问题。您可以像这样使用它:
class NetworkCounter(object):
def __init__(self):
self._count = 0
self._lock = DeferredLock()
def next(self):
return self._lock.run(self._next)
def _next(self):
self._count += 1
recording = self._record(self._count)
def recorded(ignored):
return self._count
recording.addCallback(recorded)
return recording
def _record(self, value):
return http.GET(
b"http://example.com/record-count?value=%d" % (value,))
首先,请注意NetworkCounter
实例创建自己的DeferredLock
实例。 DeferredLock
的每个实例都是不同的,并且独立于任何其他实例运行。参与使用关键部分的任何代码都需要使用相同的DeferredLock
实例才能保护该关键部分。如果两个NetworkCounter
实例以某种方式共享状态,那么他们还需要共享一个DeferredLock
实例 - 而不是创建自己的私有实例。
接下来,看看如何使用DeferredLock.run
来调用新的_next
方法(所有应用程序逻辑都已移动到该方法中)。 NetworkCounter
(使用NetworkCounter
的应用程序代码)也不会调用包含临界区的方法。 DeferredLock
负责这样做。这就是DeferredLock
可以阻止关键部分在“相同”时间由多个操作运行的方式。在内部,DeferredLock
将跟踪操作是否已开始但尚未完成。如果操作的完成表示为Deferred
,它只能跟踪操作完成。如果您熟悉Deferred
,您可能已经猜到此示例中的(假设的)HTTP客户端API http.GET
正在返回在HTTP请求完成时触发的Deferred
。如果你还不熟悉它们,你现在应该去了解它们。
一旦表示操作结果的Deferred
触发 - 换句话说,一旦操作完成,DeferredLock
将认为关键部分“不再使用”并允许另一个操作开始执行它。它将通过检查是否有任何代码在临界区使用时尝试进入临界区来执行此操作,如果是,则将运行该操作的函数。
第三,请注意,为了序列化对关键部分的访问,DeferredLock.run
必须返回Deferred
。如果临界区正在使用并且DeferredLock.run
被调用,则无法启动另一个操作。因此,它会创建并返回一个新的Deferred
。当关键部分不再使用时,下一个操作可以开始,当该操作完成时,Deferred
调用返回的DeferredLock.run
将获得其结果。对于任何已经期待Deferred
的用户来说,这一切看起来都相当透明 - 这只是意味着操作似乎需要更长时间才能完成(尽管事实是它可能需要相同的时间才能完成但它等待一段时间才开始 - 但对挂钟的影响却是相同的。)
当然,通过简单地不首先共享状态,您可以更轻松地实现并发使用安全NetworkCounter
:
class NetworkCounter(object):
def __init__(self):
self._count = 0
def next(self):
self._count += 1
result = self._count
recording = self._record(self._count)
def recorded(ignored):
return result
recording.addCallback(recorded)
return recording
def _record(self, value):
return http.GET(
b"http://example.com/record-count?value=%d" % (value,))
此版本移动NetworkCounter.next
使用的状态,以便为调用者从实例字典中生成有意义的结果(即,它不再是NetworkCounter
实例的属性)并进入调用stack(即,它现在是一个与实现方法调用的实际帧相关联的闭合变量)。由于每次调用都会创建一个新的框架和一个新的闭包,因此并发调用现在是独立的,不需要任何类型的锁定。
最后,请注意,即使NetworkCounter.next
的此修改版仍使用self._count
,{em> 在next
的{{1}}所有调用中共享NetworkCounter
实例这在并发使用时不会对实现造成任何问题。在诸如主要与Twisted一起使用的协作式多任务系统中,在功能或操作的中间永远不会有上下文切换。在self._count += 1
和result = self._count
行之间不能有从一个操作到另一个操作的上下文切换。它们将始终以原子方式执行,并且您不需要锁定它们以避免重新引入或并发引发的损坏。
最后两点 - 通过避免共享状态和函数内部代码的原子性来避免并发错误 - 组合意味着DeferredLock
通常特别有用。作为单个数据点,在我目前的工作项目中大约75个KLOC(基于Twisted),没有使用DeferredLock
。