web-dev-qa-db-ja.com

スレッドでグローバル変数を使用する

グローバル変数をスレッドと共有するにはどうすればよいですか?

私のPythonコード例は次のとおりです。

from threading import Thread
import time
a = 0  #global variable

def thread1(threadname):
    #read variable "a" modify by thread 2

def thread2(threadname):
    while 1:
        a += 1
        time.sleep(1)

thread1 = Thread( target=thread1, args=("Thread-1", ) )
thread2 = Thread( target=thread2, args=("Thread-2", ) )

thread1.join()
thread2.join()

1つの変数を共有する2つのスレッドを取得する方法がわかりません。

67
Mauro Midolo

thread2aをグローバルとして宣言するだけで、その関数に対してローカルなaを変更する必要はありません。

def thread2(threadname):
    global a
    while True:
        a += 1
        time.sleep(1)

thread1では、a(グローバル変数をシャドウするローカル変数を作成します。必要な場合はglobal aを使用します。 )>

def thread1(threadname):
    #global a       # Optional if you treat a as read-only
    while a < 10:
        print a
76
chepner

関数内:

a += 1

コンパイラによってassign to a => Create local variable aとして解釈されますが、これは望みのものではありません。 (ローカル)aが実際に初期化されていないため、おそらくa not initializedエラーで失敗します。

>>> a = 1
>>> def f():
...     a += 1
... 
>>> f()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in f
UnboundLocalError: local variable 'a' referenced before assignment

globalキーワードを使用すると、次のように(非常に眉をひそめ、正当な理由で)必要なものを取得できます。

>>> def f():
...     global a
...     a += 1
... 
>>> a
1
>>> f()
>>> a
2

ただし、一般的には、非常に迅速に手に負えなくなるグローバル変数の使用をavoidする必要があります。これは、thread1aがいつ変更されたかを知るための同期メカニズムを持たないマルチスレッドプログラムに特に当てはまります。要するに、スレッドは複雑であり、2つ(またはそれ以上)のスレッドが動作するときにイベントが発生する順序を直感的に理解することは期待できません同じ値で。言語、コンパイラ、OS、プロセッサ...すべてが役割を果たし、速度、実用性、またはその他の理由で操作の順序を変更することを決定できます。

この種の適切な方法は、Python共有ツール( locks and friends)を使用することです。または、より良い方法として、 Queue それを共有する、例えばこのような:

from threading import Thread
from queue import Queue
import time

def thread1(threadname, q):
    #read variable "a" modify by thread 2
    while True:
        a = q.get()
        if a is None: return # Poison pill
        print a

def thread2(threadname, q):
    a = 0
    for _ in xrange(10):
        a += 1
        q.put(a)
        time.sleep(1)
    q.put(None) # Poison pill

queue = Queue()
thread1 = Thread( target=thread1, args=("Thread-1", queue) )
thread2 = Thread( target=thread2, args=("Thread-2", queue) )

thread1.start()
thread2.start()
thread1.join()
thread2.join()
44
val

threading.Lockなどのロックを使用することを検討する必要があります。詳細については lock-objects をご覧ください。

受け入れられた回答は、thread1によって10を印刷できますが、これは望みのものではありません。次のコードを実行して、バグをより簡単に理解できます。

def thread1(threadname):
    while True:
      if a % 2 and not a % 2:
          print "unreachable."

def thread2(threadname):
    global a
    while True:
        a += 1

ロックを使用すると、複数の読み取り中にaの変更を禁止できます。

def thread1(threadname):
    while True:
      lock_a.acquire()
      if a % 2 and not a % 2:
          print "unreachable."
      lock_a.release()

def thread2(threadname):
    global a
    while True:
        lock_a.acquire()
        a += 1
        lock_a.release()

スレッドが長時間変数を使用している場合、最初にローカル変数にコピーすることが適切な選択です。

4
Jason Pan

その方法を提案してくれたJason Panに感謝します。 thread1のifステートメントはアトミックではないため、そのステートメントの実行中にthread2がthread1に侵入して、到達不能なコードに到達する可能性があります。以前の投稿からのアイデアを、Python 2.7で実行した完全なデモプログラム(下記)にまとめました。

いくつかの思慮深い分析で、私たちはさらなる洞察を得ることができると確信していますが、今のところは、非原子的な振る舞いがスレッドを満たしているときに何が起こるかを示すことが重要だと思います。

# ThreadTest01.py - Demonstrates that if non-atomic actions on
# global variables are protected, task can intrude on each other.
from threading import Thread
import time

# global variable
a = 0; NN = 100

def thread1(threadname):
    while True:
      if a % 2 and not a % 2:
          print("unreachable.")
    # end of thread1

def thread2(threadname):
    global a
    for _ in range(NN):
        a += 1
        time.sleep(0.1)
    # end of thread2

thread1 = Thread(target=thread1, args=("Thread1",))
thread2 = Thread(target=thread2, args=("Thread2",))

thread1.start()
thread2.start()

thread2.join()
# end of ThreadTest01.py

予測どおり、例を実行すると、「到達不能」コードに実際に到達して出力を生成することがあります。

さらに、ロック取得/解放ペアをthread1に挿入すると、「到達不能」メッセージが出力される可能性が大幅に減少することがわかりました。メッセージを見るために、スリープ時間を0.01秒に短縮し、NNを1000に増やしました。

Thread1にロックの取得と解放のペアがある場合、メッセージがまったく表示されるとは思っていませんでしたが、そこにあります。ロックの取得/解放ペアをthread2にも挿入した後、メッセージは表示されなくなりました。 hind signtでは、thread2のincrementステートメントもおそらくアトミックではありません。

3
Krista M Hill

さて、実行例を示します:

警告!これは絶対にしないAT HOME/WORK!教室のみ;)

セマフォ、共有変数などを使用して、ラッシュ状態を回避します。

from threading import Thread
import time

a = 0  # global variable


def thread1(threadname):
    global a
    for k in range(100):
        print("{} {}".format(threadname, a))
        time.sleep(0.1)
        if k == 5:
            a += 100


def thread2(threadname):
    global a
    for k in range(10):
        a += 1
        time.sleep(0.2)


thread1 = Thread(target=thread1, args=("Thread-1",))
thread2 = Thread(target=thread2, args=("Thread-2",))

thread1.start()
thread2.start()

thread1.join()
thread2.join()

そして出力:

Thread-1 0
Thread-1 1
Thread-1 2
Thread-1 2
Thread-1 3
Thread-1 3
Thread-1 104
Thread-1 104
Thread-1 105
Thread-1 105
Thread-1 106
Thread-1 106
Thread-1 107
Thread-1 107
Thread-1 108
Thread-1 108
Thread-1 109
Thread-1 109
Thread-1 110
Thread-1 110
Thread-1 110
Thread-1 110
Thread-1 110
Thread-1 110
Thread-1 110
Thread-1 110

タイミングが正しければ、a += 100操作はスキップされます。

プロセッサはT a+100で実行され、104を取得します。しかし、停止し、次のスレッドにジャンプします。ここで、T + 1で、aの古い値a+1a == 4を実行します。したがって、5を計算します(T + 2で)ジャンプ1、スレッド1、メモリにa=104を書き込みます。スレッド2に戻ると、時間はT + 3であり、メモリにa=5を書き込みます。出来上がり!次の印刷命令は、104の代わりに5を印刷します。

非常に厄介なバグを再現してキャッチします。

1
visoft