107

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

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

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

4

5 回答 5

123

您只需要声明a为全局 in 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
于 2013-11-05T14:01:50.537 回答
52

In a function:

a += 1

will be interpreted by the compiler as assign to a => Create local variable a, which is not what you want. It will probably fail with a a not initialized error since the (local) a has indeed not been 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

You might get what you want with the (very frowned upon, and for good reasons) global keyword, like so:

>>> def f():
...     global a
...     a += 1
... 
>>> a
1
>>> f()
>>> a
2

In general however, you should avoid using global variables which become extremely quickly out of hand. And this is especially true for multithreaded programs, where you don't have any synchronization mechanism for your thread1 to know when a has been modified. In short: threads are complicated, and you cannot expect to have an intuitive understanding of the order in which events are happening when two (or more) threads work on the same value. The language, compiler, OS, processor... can ALL play a role, and decide to modify the order of operations for speed, practicality or any other reason.

The proper way for this kind of thing is to use Python sharing tools (locks and friends), or better, communicate data via a Queue instead of sharing it, e.g. like this:

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()
于 2013-11-05T14:11:13.183 回答
7

应考虑使用锁,例如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()

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

于 2019-01-15T06:04:17.527 回答
4

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

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

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

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

于 2019-02-21T17:27:26.200 回答
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。

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

于 2018-09-26T09:45:11.630 回答