在线程中使用全局变量

Posted

技术标签:

【中文标题】在线程中使用全局变量【英文标题】:Using a global variable with a thread 【发布时间】:2013-11-16 10:05:15 【问题描述】:

如何与线程共享全局变量?

我的 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】:

您只需在thread2 中将a 声明为全局变量,这样您就不会修改该函数的本地a

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

thread1中,你不需要做任何特别的事情,只要你不尝试修改a的值(这会创建一个隐藏全局变量的局部变量;使用@987654327 @如果你需要)>

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

【讨论】:

要不要在文件头加lock = threading.Lock(),在thread2中加with lock:来保护a+=1 几乎可以肯定。我在一个非常狭义的意义上回答了这个问题:我如何修改一个全局变量,而不考虑如何安全地修改它。【参考方案2】:

在函数中:

a += 1

编译器会将其解释为assign to a =&gt; 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共享工具(locks 和朋友),或者更好的是,通过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()

【讨论】:

这解决了一个大问题。并且似乎几乎是正确的方法。 这是我用来解决同步问题的方法。 我有一些问题。首先,如果我有多个变量要在线程之间共享,我是否需要为每个变量设置一个单独的队列?二、为什么上述程序中的队列是同步的?不应该在每个函数中都充当本地副本吗? 这是旧的,但我还是回答了。队列本身是不同步的,不超过变量a。这是创建同步的队列的默认阻塞行为。语句 a = q.get() 将阻塞(等待)直到值 a 可用。变量q 是本地的:如果你给它分配一个不同的值,它只会在本地发生。但是代码中分配给它的队列是在主线程中定义的。 并不总是需要使用队列来在线程之间共享信息信息。 chepner 答案中的示例非常好。此外,队列并不总是正确的工具。队列很有用,例如,如果您想阻塞直到值可用。如果两个线程在共享资源上竞争,那是没有用的。最后,全局变量在线程中并不是最差的。它们实际上可以更自然。例如,您的线程可能只是一个代码块,比如一个循环,它需要自己的进程。因此,当您将循环放入函数中时,会人为地创建局部范围。【参考方案3】:

好吧,运行示例:

警告!永远不要在家里/工作中这样做! 只在教室里;)

使用信号量、共享变量等来避免紧急情况。

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。但它停止了,并跳转到下一个线程 在这里,At T+1 执行 a+1,旧值为 a,a == 4。所以它计算出 5。 跳回(在 T+2 处),线程 1,并将a=104 写入内存。 现在回到线程 2,时间是 T+3 并将a=5 写入内存。 瞧!下一条打印指令将打印 5 而不是 104。

非常讨厌的错误要被复制和捕获。

【讨论】:

请考虑添加正确的实现。这对于那些学习在线程之间共享数据的人来说非常有帮助。 添加到“待办事项”列表:)【参考方案4】:

应该考虑使用锁,例如threading.Lock。请参阅lock-objects 了解更多信息。

接受的答案可以由 thread1 打印 10,这不是您想要的。你可以运行下面的代码更容易理解这个bug。

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

如果线程长时间使用该变量,首先将其复制到局部变量是一个不错的选择。

【讨论】:

【参考方案5】:

非常感谢 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

正如预测的那样,在运行示例时,有时实际上会到达“无法访问”的代码,从而产生输出。

补充一下,当我将锁获取/释放对插入线程 1 时,我发现打印“无法访问”消息的可能性大大降低。为了看到消息,我将睡眠时间减少到 0.01 秒,并将 NN 增加到 1000。

在线程 1 中有一个锁定获取/释放对,我根本没想到会看到该消息,但它就在那里。在我将锁定获取/释放对也插入 thread2 后,该消息不再出现。在后符号中,thread2 中的增量语句可能也是非原子的。

【讨论】:

您需要两个线程中的锁,因为它们是合作的,“咨询锁”(不是“强制”)。你是对的,增量语句是非原子的。

以上是关于在线程中使用全局变量的主要内容,如果未能解决你的问题,请参考以下文章

如何在django视图函数中使用全局变量,对所有线程有效。

python多线程全局变量和锁

为啥lua语言中使用全局变量就会造成内存泄漏

使用全局变量的 WebSocket 线程

jmeter的全局变量和局部变量

使用全局变量,当多个线程同时修改静态属性第二季