2009-07-02 76 views
14

我讀this question(這你就不用看了,因爲我會複製還有什麼......我只是想給你看我的靈感)...正在修改python線程安全中的類變量嗎?

所以,如果我有一個類,計有多少實例被創建:

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

我的問題是,如果我在創建多線程富對象,被instance_count將是正確的嗎?類變量是否可以安全地從多個線程修改?

回答

21

即使在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(富.__ 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) 
-4

我會說它是線程安全的,至少在CPython實現上。 GIL將使所有的「線程」按順序運行,這樣它們就不會混淆你的引用計數。

+2

是Foo.instance_count + = 1和原子工作單元? – 2009-07-02 07:01:10

+0

也許我不明白GIL是如何工作的......但我仍然沒有看到它。不能線程1讀取instance_count。然後thread1停止。線程2讀取instance_count,然後停止。線程1修改和寫入。線程2寫入。所以你失去了一個增量? GIL如何確保線程貫穿整個+ =操作? – Tom 2009-07-02 07:02:36

+0

哈,我基本上是問薩姆弗蘭在我面前問什麼。 – Tom 2009-07-02 07:03:10

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