在线程中使用全局变量

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

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

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

251395 次浏览

You just need to declare a as a global in thread2, so that you aren't modifying an a that is local to that function.

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

In thread1, you don't need to do anything special, as long as you don't try to modify the value of a (which would create a local variable that shadows the global one; use global a if you need to)>

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

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

Well, running example:

WARNING! NEVER DO THIS AT HOME/WORK! Only in classroom ;)

Use semaphores, shared variables, etc. to avoid rush conditions.

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

and the output:

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

If the timing were right, the a += 100 operation would be skipped:

Processor executes at T a+100 and gets 104. But it stops, and jumps to next thread Here, At T+1 executes a+1 with old value of a, a == 4. So it computes 5. Jump back (at T+2), thread 1, and write a=104 in memory. Now back at thread 2, time is T+3 and write a=5 in memory. Voila! The next print instruction will print 5 instead of 104.

VERY nasty bug to be reproduced and caught.

A lock should be considered to use, such as threading.Lock. See lock-objects for more info.

The accepted answer CAN print 10 by thread1, which is not what you want. You can run the following code to understand the bug more easily.

def thread1(threadname):
while True:
if a % 2 and not a % 2:
print "unreachable."


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

Using a lock can forbid changing of a while reading more than one time:

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

If thread using the variable for long time, coping it to a local variable first is a good choice.

Thanks so much Jason Pan for suggesting that method. The thread1 if statement is not atomic, so that while that statement executes, it's possible for thread2 to intrude on thread1, allowing non-reachable code to be reached. I've organized ideas from the prior posts into a complete demonstration program (below) that I ran with Python 2.7.

With some thoughtful analysis I'm sure we could gain further insight, but for now I think it's important to demonstrate what happens when non-atomic behavior meets threading.

# 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

As predicted, in running the example, the "unreachable" code sometimes is actually reached, producing output.

Just to add, when I inserted a lock acquire/release pair into thread1 I found that the probability of having the "unreachable" message print was greatly reduced. To see the message I reduced the sleep time to 0.01 sec and increased NN to 1000.

With a lock acquire/release pair in thread1 I didn't expect to see the message at all, but it's there. After I inserted a lock acquire/release pair also into thread2, the message no longer appeared. In hind signt, the increment statement in thread2 probably also is non-atomic.