在线程中使用全局变量
- 2025-03-19 08:57:00
- admin 原创
- 54
问题描述:
如何与线程共享全局变量?
我的 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:
您只需将其声明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
解决方案 2:
在函数中:
a += 1
将被编译器解释为assign to a => Create local variable a
,这不是您想要的。它可能会失败并出现a not initialized
错误,因为(本地) a 确实尚未初始化:
>>> 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
知道什么时候a
被修改了。简而言之:线程很复杂,当两个(或更多)线程处理同一个值时,你不能指望直观地了解事件发生的顺序。语言、编译器、操作系统、处理器……都可以发挥作用,并决定出于速度、实用性或任何其他原因修改操作顺序。
解决这种问题正确的方法是使用 Python 共享工具(锁
和朋友),或者更好的是,通过队列传递数据而不是共享它,例如像这样:
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()
解决方案 3:
应考虑使用锁,例如threading.Lock
。有关详细信息,请参阅锁对象。
接受的答案可以通过线程 1 打印 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 提出这个方法。线程 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。
在线程 1 中有一个锁获取/释放对时,我根本没想到会看到该消息,但它确实在那里。在我将锁获取/释放对也插入到线程 2 中后,该消息不再出现。事后看来,线程 2 中的增量语句可能也是非原子的。
解决方案 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+1
使用 a 的旧值执行a == 4
。因此它计算出 5。跳回(在 T+2 处),线程 1,并写入a=104
内存。现在回到线程 2,时间为 T+3 并写入a=5
内存。瞧!下一个打印指令将打印 5 而不是 104。
非常讨厌的错误需要重现和捕获。
扫码咨询,免费领取项目管理大礼包!