2016-06-29 6 views
2

と仮定します。`with`ステートメント__enter__と__exit__スレッドセーフですか?

class A(object): 
    def __init__(self): 
    self.cnt = 0 
    def __enter__(self): 
    self.cnt += 1 
    def __exit__(self, exc_type, exc_value, traceback) 
    self.cnt -= 1 
  1. は、それは時にマルチスレッドself.cnt += 1が2回実行されるかもしれないことは可能ですか?
  2. 同じコンテキストマネージャインスタンスのマルチスレッドでは、何らかの形で__enter__が2回呼び出され、__exit__は1回しか呼び出されない可能性があります。self.cnt最終結果は1ですか?
+0

マルチスレッドではどういう意味ですか?どちらのコンテキストマネージャを使用していても、それぞれのスレッドに独自のインスタンスを与えても、関係はありません。アクセスされるデータがスレッド間で共有されている場合、それらへのアクセスはmutexes/lockを使用して管理する必要があります。 – metatoaster

+0

@metatoaster各スレッドには独自のコンテキストマネージャがありますが、私の同僚は '__enter__'は単なるインスタンスメソッドであると言います.2回呼び出される可能性があるのでスレッドセーフではないため、同じcontextmanagerインスタンスの' __enter__'はスレッド安全。私は失われて感じる – est

+1

各スレッドが独自のインスタンスを持っている場合、共有データはありません。 – martineau

答えて

2

いいえ、スレッドの安全性はロックによってのみ保証されます。

マルチスレッド時にself.cnt += 1が2回実行される可能性はありますか?

2つのスレッドが実行されている場合は、2回実行されます。 3つのスレッド、3回など。あなたが本当にこれによって何を意味しているのかわからない、おそらくあなたのコンテキストマネージャーとの関連でこれらのスレッドを構築/実行する方法を私たちに教えてください。

それは同じコンテキストマネージャのインスタンスのために、マルチスレッドで、何とか __enter__が二回呼ばれることと __exit__は一度だけ呼び出され、そうself.cnt最終結果が1であることは可能ですか?

はい、最終的な結果はゼロではありませんが、入力と終了の非対称な呼び出しを想定しているメカニズムではできません。あなたは必然的に最終shared_context.cntは、多くの場合でも、0でバックアップ終了しないことがわかります

from threading import Thread 

class Context(object): 
    def __init__(self): 
     self.cnt = 0 
    def __enter__(self): 
     self.cnt += 1 
    def __exit__(self, exc_type, exc_value, traceback): 
     self.cnt -= 1 

shared_context = Context() 

def run(thread_id): 
    with shared_context: 
     print('enter: shared_context.cnt = %d, thread_id = %d' % (
      shared_context.cnt, thread_id)) 
     print('exit: shared_context.cnt = %d, thread_id = %d' % (
      shared_context.cnt, thread_id)) 

threads = [Thread(target=run, args=(i,)) for i in range(1000)] 

# Start all threads 
for t in threads: 
    t.start() 

# Wait for all threads to finish before printing the final cnt 
for t in threads: 
    t.join() 

print(shared_context.cnt) 

:あなたが複数のスレッドで同じコンテキストマネージャのインスタンスを使用する場合は、そのようなエラーを再現することができ、簡単な例を構築することができます4つのオペコードに解決されている。これは主に+=オペレータによって引き起こされる

enter: shared_context.cnt = 3, thread_id = 998 
exit: shared_context.cnt = 3, thread_id = 998 
enter: shared_context.cnt = 3, thread_id = 999 
exit: shared_context.cnt = 3, thread_id = 999 
2 
... 
enter: shared_context.cnt = 0, thread_id = 998 
exit: shared_context.cnt = 0, thread_id = 998 
enter: shared_context.cnt = 1, thread_id = 999 
exit: shared_context.cnt = 0, thread_id = 999 
-1 

:すべてのスレッドが開始され、出入りがすべて多かれ少なかれペアで呼ばれていても、まったく同じコードで終了したときけれども個々のオペコードのみが保証されますGILだけであれば安全です。詳細は次の質問にあります:Is the += operator thread-safe in Python?

+0

共有コンテキストを使用しない場合、 'with shared_context:'を 'Context()'に変更しても、 '1'よりも大きくなる可能性はありますか? – est

+0

別の質問は、あなたのサンプルコードでは、 'shared_context.cnt'の最終結果(私は' joinall() ')がまだゼロであることが保証されますか? – est

+0

@est:あなたはそれを変更して自分で見ることができます。もしあなたが 'Context()'を使っていたら、手元のコードの中でスレッド間に共有データはありません。 (すべてのスレッドを意味する場合を除き、joinall()コールはありませんが、含まれている 'shared_context.cnt'に対する結果は、ゼロであることが保証されていないことを示しています。 – metatoaster

関連する問題