【问题标题】: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()

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

【问题讨论】:

    标签: python multithreading global-variables


    【解决方案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 中的增量语句可能也是非原子的。

      【讨论】:

      • 您需要两个线程中的锁,因为它们是合作的,“咨询锁”(不是“强制”)。你是对的,增量语句是非原子的。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多