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

2024-04-19 10:03:02 发布

您现在位置:Python中文网/ 问答频道 /正文

我在读this question(你不必读,因为我会复制那里的内容。。。我只想给你展示我的灵感。。。

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

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

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


Tags: 对象实例instanceself内容objectfooinit
3条回答

即使在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

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

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)

不,这不是线程安全的。几天前我也遇到过类似的问题,幸亏有一个装饰器,我才选择实现锁。这样做的好处是使代码可读:

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



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

相关问题 更多 >