线程安全的Python属性/属性?

时间:2011-08-05 06:48:20

标签: python properties attributes thread-safety

我的代码如下:

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 个答案:

答案 0 :(得分:10)

你可能需要更加思考一下线程安全的确切含义。考虑一下你是否编写了这段代码:

class SomeSharedData(object):
    def __init__(self):
        self.errors = 0

此代码完全作为您发布的代码的线程安全。在Python中为属性赋值是线程安全的:始终赋值;它可能会或可能不会被另一个线程分配的另一个值覆盖,但是你总是得到一个值或另一个值,而不是两者的混合。同样,访问该属性会为您提供当前值。

您的代码中断的地方就像您说的原始版本或简化版本一样,如下所示:

shared.errors += 1

不是线程安全的,但这是使代码安全的重点,这些是你需要注意的事情,而不是简单的获取/设置。

回答评论中的问题:

Python中的简单赋值只是重新绑定名称(不进行复制)并保证原子;你得到一个值或另一个。但是,可以覆盖属性(或下标变量),就像在上面的属性中一样。在这种情况下,属性赋值可能会中断。所以答案是属性赋值通常是安全的,但如果它已经被属性覆盖或 setattr 或类似的话,则不会。

此外,如果要替换的旧值是带有析构函数的Python类,或者包含带有析构函数的Python类的东西,那么析构函数代码可以在赋值的中间运行。 Python仍然会确保它自己的数据结构不会被破坏(所以你永远不会得到段错误),但它不会为你自己做同样的事情。对此明显的解决方法是永远不要在任何类上定义 del