多线程里,多个线程操作同一个全局对象会出现互相干扰的情况,为避免干扰,每个线程会使用局部变量,但是局部变量在单个线程的不同函数里使用需要不停的通过参数传递才能获得,这样很麻烦,python的多线程模块threading里提供了一个全局对象local, 每个线程可以使用这个对象来绑定属性值,且只能看到自己
下面通过三种情况来说明:
- 普通全局变量的多线程干扰情况
- 局部变量的冗余参数传递
- threading.local 全局变量的线程隔离
1.普通全局变量的多线程干扰情况
import threading
import time
x = 0 #全局变量,所有子线程都可以修改
def worker():
global x # 指明是全局变量
for i in range(100):
time.sleep(0.01)
x += 1
print(threading.current_thread(),x)
for i in range(10):
threading.Thread(target=worker).start()
输出:
<Thread(Thread-1, started 11104)>
<Thread(Thread-4, started 17116)>
<Thread(Thread-3, started 15488)>
<Thread(Thread-2, started 17128)>
<Thread(Thread-5, started 16872)>
<Thread(Thread-6, started 4600)>
<Thread(Thread-9, started 13152)>
<Thread(Thread-7, started 14368)>
<Thread(Thread-10, started 16768)>
<Thread(Thread-8, started 13592)>
991
993
994
992
995
996
998
999
1000
997
分析输出:如果多线程之间不干扰应该都输出100的,但是因为每个线程都对全局变量x进行修改,造成x的值被10个线程累加,最早结束的线程都累加到了991,这就是普通全局变量在多线程操作里的干扰现象
2.局部变量的冗余参数传递
import threading
import time
def worker():
x=0
for i in range(100):
time.sleep(0.01)
x += 1
print(threading.current_thread(), x)
for i in range(10):
threading.Thread(target=worker).start()
输出:
Thread(Thread-1, started 596)>
<Thread(Thread-6, started 18256)>
<Thread(Thread-4, started 13020)>
<Thread(Thread-5, started 7836)>
<Thread(Thread-8, started 15452)>
<Thread(Thread-2, started 13852)>
<Thread(Thread-7, started 15708)>
<Thread(Thread-9, started 8248)>
<Thread(Thread-3, started 14560)>
<Thread(Thread-10, started 16908)>
100
100
100
100
100
100
100
100
100
100
上面这种使用局部变量的方式可以确保每个子线程使用的都是单独的变量x,所以不会互相干扰,看上去很完美,但是如果单线程里有很多函数的话,每次调用都要显示传递x参数,例如:
import threading
import time
def worker():
x=0
for i in range(100):
time.sleep(0.01)
x += 1
print1(x)
print2(x)
print(x)
def print1(x):
print('aaaaaa',x)
def print2(x):
print('bbbbbb',x)
for i in range(10):
threading.Thread(target=worker).start()
上面这种要在单个线程里调用其它函数的时候,每次都要把局部变量x作为参数传递过去,这点类似django的视图函数,每个view函数的第一个参数都固定为request来接受请求的数据
3.threading.local 全局变量的线程隔离
import threading
import time
x = threading.local()#全局对象
def worker():
x.num = 0
for i in range(100):
time.sleep(0.01)
x.num += 1
print1()
print2()
print(threading.current_thread(),x.num)
for i in range(10):
threading.Thread(target=worker).start()
def print1():
print('aaaaaa',x.num)
def print2():
print('bbbbbb',x.num)
输出:(为了显示整洁,这里输出格式经过手动调整)
<Thread(Thread-3, started 14320)>
<Thread(Thread-1, started 16268)>
<Thread(Thread-2, started 16464)>
<Thread(Thread-9, started 16388)>
<Thread(Thread-6, started 15836)>
<Thread(Thread-8, started 12512)>
<Thread(Thread-5, started 15552)>
<Thread(Thread-10, started 1568)>
<Thread(Thread-4, started 9252)>
<Thread(Thread-7, started 18128)>
aaaaa 100
bbbbb 100
100
aaaaa 100
bbbbb 100
100
aaaaa 100
bbbbb 100
100
aaaaa 100
bbbbb 100
100
aaaaa 100
bbbbb 100
100
aaaaa 100
bbbbb 100
100
aaaaa 100
bbbbb 100
100
aaaaa 100
bbbbb 100
100
aaaaa 100
bbbbb 100
100
aaaaa 100
bbbbb 100
100
分析:
主线程里定义了threading.local全局对象x, 这个x有点类型字典,在每个子线程里都为这个x绑定了一个num属性,并且在循环里累加这个num属性,可以看到,这10个子线程对x的操作没有互相干扰,因为全局对象x为每个线程维护了独立字典,这里就相当于维护了10个独立字典,而这些字典都绑定了一个线程id, 所以每个线程来获取数据时,会通过线程的id来获取相应的字典,这就做到了每个线程只能获取到自己的数据。
同时,在调用print1(),print1()函数时,并没有像普通局部变量那样显示传递参数。
这其实就是flask里全局request对象的原理,flask的视图函数不需要像django那样需要request固定作为第一个参数,而是使用全局的request对象,import request 后就可以在任何视图函数里使用这个request对象了