x += 1
スレッドセーフであることを期待しないでください。これが機能しない例を次に示します (Josiah Carlson のコメントを参照) 。
import threading
x = 0
def foo():
global x
for i in xrange(1000000):
x += 1
threads = [threading.Thread(target=foo), threading.Thread(target=foo)]
for t in threads:
t.daemon = True
t.start()
for t in threads:
t.join()
print(x)
分解する場合foo
:
In [80]: import dis
In [81]: dis.dis(foo)
4 0 SETUP_LOOP 30 (to 33)
3 LOAD_GLOBAL 0 (xrange)
6 LOAD_CONST 1 (1000000)
9 CALL_FUNCTION 1
12 GET_ITER
>> 13 FOR_ITER 16 (to 32)
16 STORE_FAST 0 (i)
5 19 LOAD_GLOBAL 1 (x)
22 LOAD_CONST 2 (1)
25 INPLACE_ADD
26 STORE_GLOBAL 1 (x)
29 JUMP_ABSOLUTE 13
>> 32 POP_BLOCK
>> 33 LOAD_CONST 0 (None)
36 RETURN_VALUE
LOAD_GLOBAL
の値を取得する がありx
、 がありINPLACE_ADD
、次にがあることがわかりますSTORE_GLOBAL
。
両方のスレッドLOAD_GLOBAL
が連続している場合、両方とも同じ値の をロードする可能性がありますx
。次に、両方とも同じ数値にインクリメントし、同じ数値を格納します。そのため、1 つのスレッドの作業が他のスレッドの作業を上書きします。これはスレッドセーフではありません。
ご覧のとおりx
、プログラムがスレッドセーフである場合、 の最終的な値は 2000000 になりますが、ほとんどの場合、2000000 未満の数値が得られます。
ロックを追加すると、「期待される」答えが得られます。
import threading
lock = threading.Lock()
x = 0
def foo():
global x
for i in xrange(1000000):
with lock:
x += 1
threads = [threading.Thread(target=foo), threading.Thread(target=foo)]
for t in threads:
t.daemon = True
t.start()
for t in threads:
t.join()
print(x)
収量
2000000
あなたが投稿したコードが問題を示さない理由は次のとおりだと思います。
for i in range(1000):
t = threading.Thread(target = worker)
threads.append(t)
t.start()
これは、実際にはスレッド間の競合がない新しいスレッドを生成するのにかかる時間と比較して、あなたworker
の s が非常に速く完了するためです。上記の Josiah Carlson の例では、各スレッドがかなりの時間を費やしているfoo
ため、スレッドの衝突の可能性が高くなります。