使用带线程的全局变量

时间:2013-11-05 13:50:46

标签: python multithreading global-variables

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

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

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

5 个答案:

答案 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。

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