python 是否在python线程安全中修改类变量?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1072821/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 21:24:55  来源:igfitidea点击:

Is modifying a class variable in python threadsafe?

pythonmultithreading

提问by Tom

I was reading this question(which you do not have to read because I will copy what is there... I just wanted to give show you my inspiration)...

我正在阅读这个问题(你不必阅读,因为我会复制那里的内容......我只是想向你展示我的灵感)......

So, if I have a class that counts how many instances were created:

所以,如果我有一个类来计算创建了多少个实例:

class Foo(object):
  instance_count = 0
  def __init__(self):
    Foo.instance_count += 1

My question is, if I create Foo objects in multiple threads, is instance_count going to be correct? Are class variables safe to modify from multiple threads?

我的问题是,如果我在多个线程中创建 Foo 对象, instance_count 是否正确?从多个线程修改类变量是否安全?

回答by Ants Aasma

It's not threadsafe even on CPython. Try this to see for yourself:

即使在 CPython 上它也不是线程安全的。试试这个,看看你自己:

import threading

class Foo(object):
    instance_count = 0

def inc_by(n):
    for i in xrange(n):
        Foo.instance_count += 1

threads = [threading.Thread(target=inc_by, args=(100000,)) for thread_nr in xrange(100)]
for thread in threads: thread.start()
for thread in threads: thread.join()

print(Foo.instance_count) # Expected 10M for threadsafe ops, I get around 5M

The reason is that while INPLACE_ADD is atomic under GIL, the attribute is still loaded and store (see dis.dis(Foo.__init__)). Use a lock to serialize the access to the class variable:

原因是虽然 INPLACE_ADD 在 GIL 下是原子的,但该属性仍然被加载和存储(参见dis.dis(Foo.__init__))。使用锁来序列化对类变量的访问:

Foo.lock = threading.Lock()

def interlocked_inc(n):
    for i in xrange(n):
        with Foo.lock:
            Foo.instance_count += 1

threads = [threading.Thread(target=interlocked_inc, args=(100000,)) for thread_nr in xrange(100)]
for thread in threads: thread.start()
for thread in threads: thread.join()

print(Foo.instance_count)

回答by luc

No it is not thread safe. I've faced a similar problem a few days ago, and I chose to implement the lock thanks to a decorator. The benefit is that it makes the code readable:

不,它不是线程安全的。几天前我遇到了类似的问题,我选择实现锁多亏了装饰器。好处是它使代码可读:

def threadsafe_function(fn):
    """decorator making sure that the decorated function is thread safe"""
    lock = threading.Lock()
    def new(*args, **kwargs):
        lock.acquire()
        try:
            r = fn(*args, **kwargs)
        except Exception as e:
            raise e
        finally:
            lock.release()
        return r
    return new

class X:
    var = 0

    @threadsafe_function     
    def inc_var(self):
        X.var += 1    
        return X.var



回答by user84491

I would say it is thread-safe, at least on CPython implementation. The GIL will make all your "threads" to run sequentially so they will not be able to mess with your reference count.

我会说它是线程安全的,至少在 CPython 实现上是这样。GIL 将使您的所有“线程”按顺序运行,这样它们就不会干扰您的引用计数。