スレッドでのグローバル変数の使用

グローバル変数をスレッドで共有するには?

私の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()

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

ソリューション

athread2のグローバルとして宣言し、その関数にローカルなa`を変更しないようにすればよいのです。

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

スレッド1では、a`の値を変更しようとしない限り、特別なことをする必要はありません(グローバル変数の影となるローカル変数が作成されてしまいます。

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

機能では

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 "", line 1, in 
  File "", line 2, in f
UnboundLocalError: local variable 'a' referenced before assignment

非常に嫌われている、そして正当な理由のある)globalキーワードを使えば、次のようにして欲しいものが得られるかもしれません。

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

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

このような場合の適切な方法は、Pythonの共有ツール(lockksや友人)を使用することです。 など)を使うか、あるいは共有する代わりに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()
解説 (5)

例えば、ランニング。

警告!自宅や職場では絶対にやってはいけません!教室でのみ行います;)

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

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 == 4を使って、a+1を実行します。 そこで、5を計算します。 (T+2の時点で)スレッド1にジャンプバックして、メモリに `a=104 を書き込みます。 今度はスレッド2に戻って、時間はT+3で、a=5をメモリに書き込みます。 ほら!次のプリント命令では、104ではなく5がプリントされます。

これは非常に厄介なバグで、再現して捕まえる必要があります。

解説 (2)