线程安全的Python属性?
我有一段代码,像下面这样:
class SomeSharedData(object):
def __init__(self):
self._lock = RLock()
self._errors = 0
@property
def errors(self):
with self._lock:
return self._errors
@errors.setter
def errors(self, value):
with self._lock:
self._errors = value
除了还有更多的属性,比如 errors
。我这里的目标是让使用起来更简单,而不是追求效率,所以多一些锁定也没关系。
有没有更简洁的方法来定义这些属性呢?
到目前为止,我想到的最好办法是这个:
class thread_safe_property(object):
def __init__(self, name=None):
self.name = name
def __get__(self, obj, objtype):
with obj._lock:
return getattr(obj, self.name)
def __set__(self, obj, val):
with obj._lock:
setattr(obj, self.name, val)
class SomeSharedData(object):
def __init__(self):
self._lock = RLock()
self._errors = 0
errors = thread_safe_property('_errors')
有什么想法?更好的方法吗?
原来的代码和新的方法都有可能出现竞争条件,比如在 data.errors += 1
这样的语句上,但我很少需要执行这些操作,所以在需要的地方我会添加一些解决办法。
谢谢!
1 个回答
你可能需要更仔细地想想“线程安全”到底是什么意思。想象一下,如果你写了下面这段代码:
class SomeSharedData(object):
def __init__(self):
self.errors = 0
这段代码的“线程安全”程度和你之前发的代码是完全相同的。在Python中,给一个属性赋值是线程安全的:这个值总是会被赋上去;虽然它可能会被另一个线程赋的值覆盖,但你总是能得到一个值,而不会得到两个值混在一起的情况。同样,访问这个属性时,你得到的也是当前的值。
你的代码出问题的地方在于,无论是你原来的代码还是简化版,像下面这样的行:
shared.errors += 1
并不是线程安全的,但这正是让你的代码安全的关键所在,这些是你需要注意的地方,而不是简单的获取或设置值。
针对评论中的问题:
在Python中,简单的赋值实际上是重新绑定一个名字(而不是复制),这是保证原子性的;你要么得到一个值,要么得到另一个值。然而,给一个属性(或者带下标的变量)赋值可能会被覆盖,就像你上面提到的属性那样。在这种情况下,属性赋值可能会出问题。所以答案是,属性赋值通常是安全的,但如果它被属性或setattr等覆盖了,就不一定安全了。
另外,如果被替换的旧值是一个有析构函数的Python类,或者是包含有析构函数的Python类的东西,那么析构函数的代码可能会在赋值的过程中运行。Python会确保它自己的数据结构不会被破坏(所以你不会遇到段错误),但对于你自己的数据结构就不一定了。解决这个问题的明显方法是,绝对不要在你的任何类中定义del。