2009-07-02 54 views
14

私はカウントクラスを持っている場合、私は、だから... ...(私はそこにあるものをコピーしますので、あなたが、私はちょうどショーの私のインスピレーションをあなたに与えたいと思った...読む必要はありません)Pythonスレッドセーフでクラス変数を変更していますか?

this questionを読んでいましたどのように多くのインスタンスを作成されました:私は、複数のスレッドではFooオブジェクトを作成する場合

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 

理由は(dis.dis(フー.__ init__)を参照)INPLACE_ADDはGILの下で、原子である一方で、属性がまだロードされ、ストアされていることです。ロックを使用してクラス変数へのアクセスをシリアル化します。

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) 
+0

あなたの2番目の例では、スレッドターゲットをinc_byの代わりにinterlocked_incにしたいと考えています。 – tgray

+0

ありがとうございました。あまりにもリベラルなコピーアンドペーストプログラミングは時々私と一緒に追いつく。 –

+0

Ants Aasmaありがとう:-)これは私が疑った通りです。私にそれを証明してくれてありがとう。 tgrayが指摘するように、2番目のターゲットはinterlocked_incでなければなりません。しかし、一度それを変更すると...完璧に見えます。 – Tom

-4

私は、少なくともCPythonの実装ではスレッドセーフであると言います。 GILはすべての "スレッド"を順番に実行させるので、参照カウントを混乱させることはありません。

+2

Foo.instance_count + = 1とアトミック作業単位ですか? –

+0

私はGILの仕組みを理解していないかもしれませんが、私はまだそれを見ません。 Thread1はinstance_countを読み取れません。次に、thread1が停止します。スレッド2はinstance_countを読み取り、停止します。 Thread1は変更と書き込みを行います。スレッド2が書き込みます。だからあなたは増分を失う? GILはスレッドが+ =操作全体をどのように実行するのを保証しますか? – Tom

+0

ハ、私は基本的にサムサフロンが私の前で尋ねたことを尋ねていた。 – Tom

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 
+0

トピック外ですが、例外ハンドラの後に "else:"セクションへの2つのlock.release()呼び出しを削除できますか? –

+0

最後のセクションでは意味がありますか?例外が発生したときにelseでそれを行うのは解放されません。 – luc

+0

ああ、それは私の意図です。ありがとう! –

関連する問題