我的代码如下:
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
等语句的可能竞争条件的影响,但我很少需要执行这些操作,因此我会在需要时添加变通方法。
谢谢!
答案 0 :(得分:10)
你可能需要更加思考一下线程安全的确切含义。考虑一下你是否编写了这段代码:
class SomeSharedData(object):
def __init__(self):
self.errors = 0
此代码完全作为您发布的代码的线程安全。在Python中为属性赋值是线程安全的:始终赋值;它可能会或可能不会被另一个线程分配的另一个值覆盖,但是你总是得到一个值或另一个值,而不是两者的混合。同样,访问该属性会为您提供当前值。
您的代码中断的地方就像您说的原始版本或简化版本一样,如下所示:
shared.errors += 1
不是线程安全的,但这是使代码安全的重点,这些是你需要注意的事情,而不是简单的获取/设置。
回答评论中的问题:
Python中的简单赋值只是重新绑定名称(不进行复制)并保证原子;你得到一个值或另一个。但是,可以覆盖属性(或下标变量),就像在上面的属性中一样。在这种情况下,属性赋值可能会中断。所以答案是属性赋值通常是安全的,但如果它已经被属性覆盖或 setattr 或类似的话,则不会。
此外,如果要替换的旧值是带有析构函数的Python类,或者包含带有析构函数的Python类的东西,那么析构函数代码可以在赋值的中间运行。 Python仍然会确保它自己的数据结构不会被破坏(所以你永远不会得到段错误),但它不会为你自己做同样的事情。对此明显的解决方法是永远不要在任何类上定义 del 。