在线程中使用全局变量


84

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

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

我不知道如何让两个线程共享一个变量。

Answers:


97

您只需要在中声明a为global 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

47

在函数中:

a += 1

将被编译器解释为assign to a => Create local variable a,这不是您想要的。a not initialized由于(local)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进行了修改。简而言之:线程很复杂,并且当两个(或更多)线程以相同的值工作时,您不能期望对事件发生的顺序有直观的了解。语言,编译器,OS,处理器...都可以发挥作用,并出于速度,实用性或任何其他原因决定更改操作顺序。

这种事情的正确方法是使用Python共享工具( 和朋友),或者更好的方法是通过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()

这解决了一个大问题。而且似乎是正确的做法。
阿比德蒙

这就是我用来解决同步问题的方法。
张隆琪

1
我有一些问题。首先,如果我有多个变量要在线程之间共享,那么每个变量是否需要一个单独的队列?其次,为什么上面程序中的队列是同步的?难道每个函数都不应该在每个函数中充当本地副本吗?

这很老,但我还是回答。队列本身不同步,仅与变量不同步a。这是创建同步的队列的默认阻止行为。该语句a = q.get()将阻塞(等待),直到值a可用为止。变量q是local:如果您给它分配了一个不同的值,它将仅在本地发生。但是代码中分配给它的队列是主线程中定义的队列。

1
不一定总是需要使用队列在线程之间共享信息信息。chepner答案中的示例非常好。同样,队列也不总是正确的工具。例如,如果您要阻塞直到该值可用,则队列很有用。如果两个线程在共享资源上竞争,这是没有用的。最后,全局变量在线程中并不差。实际上它们可以更自然。例如,您的线程可能只是一个代码块,例如一个循环,需要它自己的进程。因此,当您将循环放入函数中时,将自动创建局部作用域。

5

应该考虑使用锁,例如threading.Lock。有关更多信息,请参见锁定对象

可接受的答案可以通过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

非常感谢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中有一个锁获取/释放对,我完全没想到看到该消息,但是它在那里。将锁获取/释放对也插入thread2后,该消息不再出现。在后符号中,thread2中的增量语句可能也是非原子的。


1
您需要两个线程中的锁,因为它们是协作的“建议锁”(不是“强制性”)。您说对了,增量声明是非原子的。
Darkonaut

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执行a == 4。因此它计算出5。跳回(在T + 2),线程1,然后写入a=104内存。现在回到线程2,时间是T + 3并写入a=5内存。瞧!下一条打印指令将打印5而不是104。

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


请考虑也添加正确的实现。对于那些学习在线程之间共享数据的人来说,这将非常有帮助。
JS。

1
已添加到“待办事项”列表中:)
visoft19年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.