我正在阅读this question(你不必阅读,因为我会复制那里的东西......我只是想告诉你我的灵感)......
所以,如果我有一个类来计算创建了多少个实例:
class Foo(object):
instance_count = 0
def __init__(self):
Foo.instance_count += 1
我的问题是,如果我在多个线程中创建Foo对象,instance_count是否正确?是否可以安全地从多个线程修改类变量?
答案 0 :(得分:24)
即使在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 .__ 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)
答案 1 :(得分:8)
不,它不是线程安全的。我几天前遇到过类似的问题,我选择通过装饰器实现锁定。好处是它使代码可读:
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
答案 2 :(得分:0)
在luc的回答之后,这是一个简化的装饰器,它使用filter
上下文管理器和少量with
代码来加速测试。在不使用@synchronized装饰器的情况下进行尝试,以查看区别。
__main__
import concurrent.futures
import functools
import logging
import threading
def synchronized(function):
lock = threading.Lock()
@functools.wraps(function)
def wrapper(self, *args, **kwargs):
with lock:
return function(self, *args, **kwargs)
return wrapper
class Foo:
counter = 0
@synchronized
def increase(self):
Foo.counter += 1
if __name__ == "__main__":
foo = Foo()
print(f"Start value is {foo.counter}")
with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
for index in range(200000):
executor.submit(foo.increase)
print(f"End value is {foo.counter}")
答案 3 :(得分:-4)
我认为它是线程安全的,至少在CPython实现上是这样。 GIL将使所有“线程”按顺序运行,这样它们就不会弄乱您的引用计数。