57

実験用にスレッドセーフではないコードのチャンクを作成したいのですが、これらは2つのスレッドが呼び出す関数です。

c = 0

def increment():
  c += 1

def decrement():
  c -= 1

このコードスレッドは安全ですか?

そうでない場合は、なぜそれがスレッドセーフではないのか、そしてどのようなステートメントが通常スレッドセーフでない操作につながるのかを理解できますか。

スレッドセーフの場合、どうすれば明示的に非スレッドセーフにすることができますか?

4

8 に答える 8

117

いいえ、このコードは絶対に、明らかにスレッドセーフではありません。

import threading

i = 0

def test():
    global i
    for x in range(100000):
        i += 1

threads = [threading.Thread(target=test) for t in range(10)]
for t in threads:
    t.start()

for t in threads:
    t.join()

assert i == 1000000, i

一貫して失敗します。

i + = 1は、4つのオペコードに解決されます。iをロードし、1をロードし、2つを追加して、iに格納します。Pythonインタープリターは、100オペコードごとにアクティブなスレッドを切り替えます(1つのスレッドからGILを解放して、別のスレッドがGILを使用できるようにします)。(これらは両方とも実装の詳細です。)競合状態は、ロードと保存の間に100オペコードのプリエンプションが発生し、別のスレッドがカウンターのインクリメントを開始できるようになるときに発生します。中断されたスレッドに戻ると、古い値の「i」で続行し、その間に他のスレッドによって実行された増分を元に戻します。

スレッドセーフにするのは簡単です。ロックを追加します。

#!/usr/bin/python
import threading
i = 0
i_lock = threading.Lock()

def test():
    global i
    i_lock.acquire()
    try:
        for x in range(100000):
            i += 1
    finally:
        i_lock.release()

threads = [threading.Thread(target=test) for t in range(10)]
for t in threads:
    t.start()

for t in threads:
    t.join()

assert i == 1000000, i
于 2009-11-11T23:37:10.687 に答える
31

global c(注:コードを機能させるには、各関数で必要になります。)

このコードスレッドは安全ですか?

いいえ。CPythonでは単一のバイトコード命令のみが「アトミック」であり、+=関連する値が単純な整数であっても、単一のオペコードにならない場合があります。

>>> c= 0
>>> def inc():
...     global c
...     c+= 1

>>> import dis
>>> dis.dis(inc)

  3           0 LOAD_GLOBAL              0 (c)
              3 LOAD_CONST               1 (1)
              6 INPLACE_ADD         
              7 STORE_GLOBAL             0 (c)
             10 LOAD_CONST               0 (None)
             13 RETURN_VALUE        

したがって、1つのスレッドがcと1がロードされた状態でインデックス6に到達し、GILを放棄し、別のスレッドを入れて、を実行しincてスリープし、GILを最初のスレッドに戻します。

いずれにせよ、アトミックとは、信頼してはいけない実装の詳細です。バイトコードはCPythonの将来のバージョンで変更される可能性があり、GILに依存しないPythonの他の実装では結果がまったく異なります。スレッドセーフが必要な場合は、ロック機構が必要です。

于 2009-11-11T19:33:33.920 に答える
16

確かに私はロックを使用することをお勧めします:

import threading

class ThreadSafeCounter():
    def __init__(self):
        self.lock = threading.Lock()
        self.counter=0

    def increment(self):
        with self.lock:
            self.counter+=1


    def decrement(self):
        with self.lock:
            self.counter-=1

同期されたデコレータは、コードを読みやすくするのにも役立ちます。

于 2011-07-20T11:27:40.543 に答える
12

コードがスレッドセーフではないことを証明するのは簡単です。重要な部分でスリープを使用することにより、競合状態が発生する可能性を高めることができます(これは単に低速のCPUをシミュレートします)。ただし、コードを十分に長く実行すると、最終的には関係なく競合状態が表示されます。

from time import sleep
c = 0

def increment():
  global c
  c_ = c
  sleep(0.1)
  c = c_ + 1

def decrement():
  global c
  c_ = c
  sleep(0.1)
  c  = c_ - 1
于 2009-11-11T19:39:29.183 に答える
5

簡単な答え:いいえ。

長い答え:一般的にはそうではありません。

CPythonのGILは単一のオペコードをスレッドセーフにしますが、これは一般的な動作ではありません。加算のような単純な操作でさえ、アトミックな命令であるとは思わないかもしれません。追加は、別のスレッドが実行されたときに半分しか実行されない場合があります。

また、関数が複数のオペコードの変数にアクセスするとすぐに、スレッドセーフは失われます。関数本体をロックでラップすると、スレッドセーフを生成できます。ただし、ロックは計算コストが高く、デッドロックが発生する可能性があることに注意してください。

于 2009-11-11T19:29:34.257 に答える
2

単一のオペコードはGILのためにスレッドセーフですが、他には何もありません。

import time
class something(object):
    def __init__(self,c):
        self.c=c
    def inc(self):
        new = self.c+1 
        # if the thread is interrupted by another inc() call its result is wrong
        time.sleep(0.001) # sleep makes the os continue another thread
        self.c = new


x = something(0)
import threading

for _ in range(10000):
    threading.Thread(target=x.inc).start()

print x.c # ~900 here, instead of 10000

複数のスレッドで共有されるすべてのリソースには、ロックが必要です。

于 2009-11-11T19:34:44.713 に答える
2

実際にコードをスレッドセーフではなく、1万回(または実際に「悪い」ものを発生させたくない場合)に試行しなくても、「悪い」ものが実際に発生する可能性が高い場合は、明示的なスリープを使用してコードを「ジッター」することができます。

def íncrement():
    global c
    x = c
    from time import sleep
    sleep(0.1)
    c = x + 1
于 2009-11-11T19:37:02.313 に答える
0

関数のインクリメントとデクリメントがエラーなしで実行されることを確認しますか?

'c'という名前のグローバル変数を使用することをPythonに明示的に指示する必要があるため、UnboundLocalErrorが発生するはずです。

したがって、インクリメント(デクリメントも)を次のように変更します。

def increment():
    global c
    c += 1

あなたのコードはスレッドセーフではないと思います。Pythonのスレッド同期メカニズムに関するこの記事が役立つ場合があります。

于 2009-11-11T19:36:53.687 に答える