グローバル変数をスレッドと共有するにはどうすればよいですか?
私の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つのスレッドを取得する方法がわかりません。
thread2
でa
をグローバルとして宣言するだけで、その関数に対してローカルな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
関数内:
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する必要があります。これは、thread1
がa
がいつ変更されたかを知るための同期メカニズムを持たないマルチスレッドプログラムに特に当てはまります。要するに、スレッドは複雑であり、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()
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()
スレッドが長時間変数を使用している場合、最初にローカル変数にコピーすることが適切な選択です。
その方法を提案してくれた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ステートメントもおそらくアトミックではありません。
さて、実行例を示します:
警告!これは絶対にしない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+1
でa == 4
を実行します。したがって、5を計算します(T + 2で)ジャンプ1、スレッド1、メモリにa=104
を書き込みます。スレッド2に戻ると、時間はT + 3であり、メモリにa=5
を書き込みます。出来上がり!次の印刷命令は、104の代わりに5を印刷します。
非常に厄介なバグを再現してキャッチします。