Python Twisted的延迟

2024-10-03 00:22:01 发布

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

有人能举个例子解释一下什么时候和怎样使用Twisted的DeferredLock。在

我有一个延迟队列,我想我有一个竞争条件,我想我想防止,但我不知道如何将两者结合起来。在


Tags: 队列twisted条件例子deferredlock
1条回答
网友
1楼 · 发布于 2024-10-03 00:22:01

当您有一个异步的关键部分并且需要防止重叠(可以说是“并发”)执行时,请使用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方法将如何产生“损坏”结果:

^{pr2}$

给出结果:

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将跟踪一个操作是否已启动和尚未完成。但是,只有当操作的完成被表示为Deferred时,它才能跟踪操作完成情况。如果您熟悉Deferred,那么您可能已经猜到本例中的(假设的)HTTP客户机API http.GET正在返回一个在HTTP请求完成时触发的Deferred。如果你还不熟悉它们,你现在就应该去看看它们。在

一旦代表操作结果的Deferred被触发-换句话说,一旦操作完成,DeferredLock将考虑关键部分“停止使用”,并允许另一个操作开始执行它。它将通过检查是否有任何代码试图在临界区被使用时进入临界区来实现这一点,如果是这样,它将为该操作运行函数。在

第三,请注意,为了序列化对关键部分的访问,DeferredLock.run必须返回一个Deferred。如果critical部分正在使用并且调用了DeferredLock.run,则它无法启动另一个操作。因此,它创建并返回一个新的Deferred。当critical部分停止使用时,可以开始下一个操作,当该操作完成时,DeferredLock.run调用返回的Deferred将获得其结果。对于那些已经期待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实例的属性)并转移到调用堆栈(即,它现在是与实现方法调用的实际框架关联的闭合变量)。因为每个调用都会创建一个新的帧和一个新的闭包,所以并发调用现在是独立的,并且不需要任何类型的锁定。在

最后,请注意,即使NetworkCounter.next的这个修改版本仍然使用self._count,它在一个NetworkCounter实例上的所有对next的调用之间共享{},但当它同时使用时,不会对实现造成任何问题。在一个协同多任务系统中,如主要用于Twisted的系统,在函数或操作的中间从来没有上下文切换。在self._count += 1result = self._count行之间不能有从一个操作到另一个操作的上下文切换。它们将始终以原子方式执行,您不需要在它们周围加锁以避免重新进入或并发导致的损坏。在

最后两点——通过避免共享状态和函数内代码的原子性来避免并发错误——结合起来意味着DeferredLock并不经常特别有用。作为一个数据点,在我当前的工作项目中大约75kloc(基于严重扭曲的),没有使用DeferredLock。在

相关问题 更多 >