Python实例变量是否线程安全?

42

好的,请先检查以下代码:

class DemoClass():

    def __init__(self):
        #### I really want to know if self.Counter is thread-safe. 
        self.Counter = 0

    def Increase(self):
        self.Counter = self.Counter + 1

    def Decrease(self):
        self.Counter = self.Counter - 1

    def DoThis(self):
        while True:
            Do something

            if A happens:
                self.Increase()
            else:
                self.Decrease()

            time.sleep(randomSecs)

    def DoThat(self):
        while True:
            Do other things

            if B happens:
                self.Increase()
            else:
                self.Decrease()

            time.sleep(randomSecs)

    def ThreadSafeOrNot(self):
        InterestingThreadA = threading.Thread(target = self.DoThis, args = ())
        InterestingThreadA.start()

        InterestingThreadB = threading.Thread(target = self.DoThat, args = ())
        InterestingThreadB.start()

我面临与上述相同的情况。我真的想知道self.Counter是否是线程安全的,如果不是,我有哪些选项?我只能想到使用threading.RLock()来锁定此资源,还有更好的想法吗?

5个回答

51

你可以使用锁、重入锁、信号量、条件变量、事件和队列。
而且这篇文章对我帮助很大
看一下:Laurent Luce的博客


这并没有直接回答所提出的问题。 - undefined

32
使用实例字段 self.Counter 是线程安全或者说是"原子的"(参考 这里 或者 这里)。读取它或者给它赋一个单一的值 - 即便这个值需要4字节的内存,你也永远不会得到一个被部分修改过的值。但是操作 self.Counter = self.Counter + 1 不是,因为它会先读取该字段的值,然后再写回去 - 另一个线程可能会在它读取和写回之间更改该字段的值。
所以你需要使用锁来保护整个操作。
由于方法体基本上就是整个操作,你可以使用装饰器来完成。参见这个答案的例子:https://dev59.com/8HRB5IYBdhLWcg3w26x3#490090

1
出于好奇,"实例字段是线程安全的"是什么意思? - Eli Bendersky
3
我猜他的意思是,诸如 self.Counter = Value 这样的操作是线程安全的。请查看我刚找到的这篇文章:http://effbot.org/pyfaq/what-kinds-of-global-value-mutation-are-thread-safe.htm - Shane
1
赋值不是线程安全的。假设您有self.Counter=1,然后是self.Counter=2,接着是x=self.Counter。那么x可以得到1或2,这取决于线程切换,这使得赋值不是线程安全的。 - Basel Shishani
3
我所说的是,一个单独的赋值操作是原子性的(除了64位类型)。请参见https://dev59.com/N2445IYBdhLWcg3wpb8P。 - Aaron Digulla
在问题的代码中,同一个实例在线程之间共享。 - Aaron Digulla
显示剩余3条评论

15

不,它不是线程安全的 - 两个线程本质上同时修改同一个变量。而解决方案是使用 threading 模块中的锁定机制之一。

顺便说一下,self.Counter 是一个实例变量,不是类变量。


我认为只有类变量不是线程安全的。实例变量应该是线程安全的,除非类实例是全局创建的,并传递给线程。你怎么看? - variable
@variable因为实例可以传递到不同的线程,所以你的观点没有意义。无论其实现如何,事物都是或者不是线程安全的。 - Maxime de Pachtere

2

self.Counter 是一个实例变量,每个线程都有一份副本。

如果你在 __init__() 外声明该变量,它将成为一个类变量。 所有该类的实例都会共享该变量。


8
多个线程肯定可以访问同一个对象实例,是吗? - Harry Johnston
1
@HarryJohnston 要非常小心。它可能会增加相当多的复杂性。你可能需要进行某种形式的锁定。 - Austin Henley
4
是的,这就是重点。原帖的作者想知道实例变量是否线程安全。 - Harry Johnston
我认为只有类变量不是线程安全的。实例变量应该是线程安全的,除非类实例是全局创建的,并传递给线程。你怎么看? - variable

1

Atomos库提供了Python原语和对象的原子(线程安全)包装器,包括原子计数器。它使用单写/多读锁。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接