5

私のコードは

import threading

counter = 0

def worker():
    global counter
    counter += 1

if __name__ == "__main__":
    threads = []
    for i in range(1000):
        t = threading.Thread(target = worker)
        threads.append(t)
        t.start()
    for t in threads:
        t.join()

    print counter

共有リソース、つまりカウンター変数を保護するためにロックを使用していないため、結果が 1000 未満の数値になると予想していますが、カウンターは常に 1000 です。理由はわかりません。counter += 1Pythonのアトミック操作はありますか?

GILを使用してアトミックなPythonの操作は何ですか?

4

1 に答える 1

8

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ため、スレッドの衝突の可能性が高くなります。

于 2013-02-25T02:01:12.310 に答える