[英]Inheriting methods that run a non-reentrant critical section
我有一个带有多个线程的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
装饰器的函数具有两个属性:
enter
和leave
要小心)。 第二个属性表示该锁不是可重入的锁。 在临界区中调用“临界区”函数是无效的,因为不能保证必要的不变式。
enter
和leave
的实现检查以下属性:如果资源当前是所有者(即使是该线程本身),则线程无法调用enter
,而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()
被执行时,资源已经拥有。 在调用基类的方法时,派生类可能已经破坏了该对象,这违背了Base.foo
从处于已知稳定状态的对象开始的假设。
显而易见的解决方案是将每个关键部分函数转换为一对函数:外部函数(仅可从外部调用,并且永远不会在修改资源行为的类中覆盖),以及内部函数(仅可用于仅可从派生方法调用)。
编辑:这是一个正确的不可重入版本。
您可以让装饰器接受参数。 所以@dec(x); def f() ...
@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)
你好,我是昨天的。 您的根本问题是您简化了情况。 您将两件事混为一谈:进入/离开关键部分,并假设/声明资源的不变式。 但是资源的不变式在关键部分的中间也可能是正确的,而这正是您说允许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 = Falseassert(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)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.