继承运行不可重入临界区的方法

时间:2016-02-17 00:15:51

标签: python decorator

我有一个Python程序,其中包含多个操作共享资源的线程。我使用该对象中的方法,使用此资源上的对象和操作对此资源进行建模。在关键部分中,需要将许多操作作为原子操作来执行。由于这些函数遵循一个共同的模式,我使用了一个装饰器:

def critical_section(inner):
    def outer(self, *args, **kwargs):
        self.enter()
        inner(self, *args, **kwargs)
        self.leave()
    return outer

真实代码比这更复杂,例如它处理异常,但这应该足以得到我正在做的事情的要点。示例用法(我简化了所有与线程相关的内容,只检查资源所有权):

class Base:
    def __init__(self):
        self.owner = None
    def enter(self):
        assert(self.owner == None)
        self.owner = True
    def leave(self):
        assert(self.owner == True)
        self.owner = None
    @critical_section
    def foo(self):
        print('foo') # Dangerous stuff, must run atomically and not fail

critical_section装饰器的函数有两个属性:

  • 在保持资源锁定的情况下执行函数的代码(enterleave负责处理。)
  • 该函数假设资源在进入时处于稳定状态,并在退出时保持稳定状态。

第二个属性意味着锁不是可重入的锁。在已经处于关键部分时调用“临界区”功能是无效的,因为不能保证必要的不变量。

enterleave的实现检查这些属性:如果资源当前拥有(即使是该线程本身),线程也无法调用enter,{{1将资源的所有权赋予调用线程; enter对称地需要所有权并放弃。

这个架构很好地帮助了我,直到我想拥有具有类似结构的多个资源,所以我开始使用继承。

leave

现在我们遇到了问题:装饰器从根本上说是继承 - 不友好。

class Derived(Base):
    @critical_section
    def foo(self):
        print('fie') # Additional stuff to run first
        Base.foo(self)

base = Base() base.foo() derived = Derived() derived.foo() 的调用失败,因为执行derived.foo()时,资源已经拥有。在调用基类的方法时,派生类可能已经破坏了对象,违反了Base.foo()以处于已知稳定状态的对象开始的假设。

显而易见的解决方案是将每个关键部分函数转换为一对函数:外部函数(意味着从外部调用,而不会在修改资源行为的类中重写)和内部函数(仅用派生方法调用)。

  1. Python装饰器能帮助我用最少的样板来定义这样的函数对吗?
  2. 是否有更好的架构可以最大限度地提高进入和退出关键部分的清晰度,并最大限度地降低误用风险? (动态检查很好,但源代码的明显正确性更好。)

2 个答案:

答案 0 :(得分:1)

编辑:这是一个正确的非重入版本。

你可以让装饰者接受参数。因此@dec(x); def f() ...将被称为dec(x)(f)(args)。所以,我们critical_section接受一个字符串(@critical_section("Base"))并且每个字符串都有一个锁。

def critical_section(ident):
    def _critical_section(inner):
        def outer(self, *args, **kwargs):
            self.enter(ident)
            inner(self, *args, **kwargs)
            self.leave(ident)
        return outer
    return _critical_section

class Base:
    def __init__(self):
        self.owner = {}
    def enter(self, ident):
        assert(ident not in self.owner)
        self.owner[ident] = True
    def leave(self, ident):
        assert(ident in self.owner)
        del self.owner[ident]
    @critical_section("Base")
    def foo(self):
        print('foo') # Dangerous stuff, must run atomically and not fail

class Derived(Base):
    @critical_section("Derived")
    def foo(self):
        print('fie') # Additional stuff to run first
        Base.foo(self)

答案 1 :(得分:0)

你好,我是昨天来的。你的根本问题是你过分简化了这种情况。你混淆了两件事:进入/离开关键部分,以及假设/断言资源的不变量。但是,在关键部分的中间,资源的不变量也是正确的,当你说Derived.foo方法被允许调用时,这正是你试图传达的内容Base.foo(在执行期间的某个特定时刻)。

您可以在Python中对此进行建模,但确实有点麻烦。

def critical_section(inner):
    def outer(self, *args, **kwargs):
        self.enter()
        inner(self, *args, **kwargs)
        self.leave()
    return outer

class Base:
    def __init__(self):
        self.owner = None
        self.invariants = True
    def enter(self):
        assert(self.invariants)
        self.invariants = False
        assert(self.owner == None)
        self.owner = True
    def leave(self):
        assert(self.owner == True)
        self.owner = None
        self.invariants = True
    @critical_section
    def foo(self):
        print('foo') # Dangerous stuff, must run atomically and not fail
class Derived(Base):
    @critical_section
    def foo(self):
        print('fie') # Additional stuff to run first
        self.invariants = True
        Base.foo(self)

所有者的东西应该是现实世界中的折返锁。在不稳定的状态下,不存在不可重入以防止修改资源,而是检查这种不变量。

但是当“检查不变量”等于“我声明在代码中此时验证了有用的不变量”时,所有这些都很复杂并且不值得。通过静态分析检查不变量,这将是另一个故事,但是你不会使用Python。

回到你的问题,在需要时访问内部函数,Python确实很容易。将内部函数存储在修饰函数的属性中。

def critical_section(inner):
    def outer(self, *args, **kwargs):
        self.enter()
        inner(self, *args, **kwargs)
        self.leave()
    outer.inner_function = inner
    return outer
…
class Derived(Base):
    @critical_section
    def foo(self):
        print('fie') # Additional stuff to run first
        Base.inner.foo(self)