如何在Python中避免类属性初始化时的死锁,涉及锁、继承和线程?
我正在尝试实现线程安全的代码,但遇到了一些简单的问题。我搜索了一下,没有找到解决方案。
让我用抽象代码来描述这个问题:
import threading
class A(object):
sharedLock = threading.Lock()
shared = 0
@classmethod
def getIncremented(cls):
with cls.sharedLock:
cls.shared += 1
return cls.shared
class B(A):
pass
class C(A):
@classmethod
def getIncremented(cls):
with cls.sharedLock:
cls.shared += B.getIncremented()
return cls.shared
我想定义一个类A,然后让它有很多子类,比如用于枚举或者懒加载变量——具体用途不重要。我已经完成了单线程版本,现在想更新为多线程。
这段代码应该能产生这样的结果:
id(A.sharedLock) 11694384
id(B.sharedLock) 11694384
id(C.sharedLock) 11694384
我的意思是,class A
中的锁在class B
中也会锁住,所以这不好,因为第一次进入class B
时也会锁住class A
和class C
。如果C
使用B
,就会导致死锁。
我可以使用RLock
,但这不是一个有效的编程模式,我不确定这样是否会导致更严重的死锁。
我该如何在类初始化时更改sharedLock的值,以便让id(A.sharedLock) != id(B.sharedLock)
,同样适用于A
和C
,以及B
和C
?
我该如何在Python中通用地挂钩类的初始化,以便更改一些类变量?
这个问题并不复杂,但我不知道该怎么处理。
3 个回答
正如你所注意到的,如果把共享锁作为类的属性暴露出来,这些锁会被子类共享。
你可以通过在每个子类中重新定义锁来解决这个问题:
class B(A):
sharedLock = threading.Lock()
你甚至可以使用 metaclass(元类)来实现这个,但我建议你不要这样做。看起来你可能是从错误的角度来考虑这个程序。
如果你将锁明确地分配给实例(而不是类),这个任务会简单很多。
class A(object):
def __init__(self, lock):
this.sharedLock= lock
my_lock= threading.Lock()
a= A(my_lock)
当然,这样做会遇到一个“问题”,就是你需要为每个实例明确地传递锁。这个问题通常可以通过使用工厂模式来解决,但在 Python 中,你可以简单地正确使用函数:
from functools import partial
A_with_mylock= partial(A, my_lock)
a2= A_with_mylock()
我想要继承父类的共享变量,但不想要共享父类的锁
这样做是不行的。这会导致“共享变量”的访问不安全。
sharedLock
用来保护 shared
变量。如果在递归调用中同一个 shared
变量可以被修改,那么你需要使用 RLock()
。这里的 shared
是指在所有子类之间共享的。
看起来你想要一个独立的函数(或者静态方法),而不是类方法:
def getIncremented(_lock=Lock(), _shared=[0]):
with _lock:
_shared[0] += 1
return _shared[0]
这样所有的类都会使用同一个 shared
变量(以及相应的 lock
)。
如果你希望每个类都有自己的 shared
变量(这里的 shared
是指在这个特定类的实例之间共享),那么就不要使用 cls.shared
,因为它可能会去查找父类来获取这个变量。
为了提示子类不应该直接使用某个变量,你可以使用私有变量的语法:
class A:
__shared = 0
__lock = Lock()
如果一个子类重写了一个使用 __shared
的方法,那么在代码中就不会意外地直接使用 A.__shared
。
这里有一个解决方案——这个方法可以为每个类设置独立的锁,因为它是在类的构造函数层面上实现的(元类)。感谢大家的提示和帮助,让我能写出这样的代码,效果看起来非常不错。
我也可以把变量搞混淆,但需要使用硬编码的'_A__lock',这可能会有问题,而且我还没有测试过。
import threading
class MetaA(type):
def __new__(self, name, bases, clsDict):
# change <type> behavior
clsDict['_lock'] = threading.Lock()
return super(MetaA, self).__new__(self, name, bases, clsDict)
class A(object):
__metaclass__ = MetaA
@classmethod
def getLock(cls):
return cls._lock
class B(A):
pass
print 'id(A.getLock())', id(A.getLock())
print 'id(B.getLock())', id(B.getLock())
print A.getLock() == B.getLock()