如何与线程共享全局变量?
我的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()
我不知道如何让两个线程共享一个变量。
答案 0 :(得分:74)
您只需要将a
声明为thread2
中的全局,这样您就不会修改该函数的本地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
答案 1 :(得分:41)
在一个功能中:
a += 1
将被编译器解释为assign to a => Create local variable a
,这不是您想要的。它可能会因{(1}}错误而失败,因为(本地)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
没有任何同步机制可以知道thread1
何时被修改。简而言之:线程复杂,当两个(或多个)线程处理相同的值时,您不能期望直观地了解事件发生的顺序。语言,编译器,操作系统,处理器......都可以发挥作用,并决定修改操作的顺序,以提高速度,实用性或任何其他原因。
这种事情的正确方法是使用Python共享工具(locks 和朋友)或更好的方式,通过Queue来传播数据,而不是分享数据,例如像这样:
a
答案 2 :(得分:4)
应该考虑使用锁,例如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()
如果线程长时间使用该变量,那么最好先将其处理为局部变量。
答案 3 :(得分:2)
非常感谢Jason Pan提出该方法。线程1的if语句不是原子的,因此在执行该语句时,线程2可能会侵入线程1,从而允许到达不可访问的代码。我已经将以前的文章中的想法整理成一个完整的演示程序(如下所示),该程序是我在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
正如预测的那样,在运行示例时,有时实际上会到达“无法访问”的代码,从而产生输出。
仅需添加,当我在线程1中插入一个锁获取/释放对时,我发现打印“不可达”消息的可能性大大降低了。要查看该消息,我将睡眠时间减少到0.01秒,并将NN增加到1000。
在thread1中有一个锁获取/释放对,我完全没希望看到该消息,但是它在那里。将锁获取/释放对也插入线程2后,该消息不再出现。在后符号中,线程2中的增量语句可能也是非原子的。
答案 4 :(得分:1)
运行示例:
警告!永远不要在家里/办公室这样做!仅在教室里;)
使用信号量,共享变量等避免紧急情况。
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+1
执行a == 4
。因此它计算5。
回跳(在T + 2),线程1,并将a=104
写入内存。
现在回到线程2,时间为T + 3,并将a=5
写入内存。
瞧!下一条打印指令将打印5而不是104。
要复制和捕获非常讨厌的错误。