前言
python多线程的特征之一是:
所有子线程共享主线程的对象。
所有子线程共享主线程的对象。
所有子线程共享主线程的对象。
重要的事情说3遍
这在有些时候很方便(当然不注意也会造成bug。),但有些时候,我们确实需要每个线程仅维护自己的本地数据,即私有变量。
一、threading.local()对象
线程本地数据是特定线程的数据。管理线程本地数据,只需要创建一个local (或者一个子类型)的实例并在实例中储存属性:
mydata = threading.local()
mydata.x = 1
在不同的线程中,实例的值会不同。
二、两种用法
用法1:实例化
介绍该用法之前,看看如果没有local对象介入的情况下,子线程共享主线程对象的含义。
代码如下(示例):
from threading import Thread
from threading import local
#创建一个类,继承自Thread对象
class subThread(Thread):
#重载构造函数
def __init__(self,func,*args):
#调用基类的构造函数,必要的一步
Thread.__init__(self)
self.func = func
self.args = args
#重载Thread.run()方法
def run(self):
#带一个*号的参数,代表可以收集位置参数
#若是带两个*号的参数,代表可以收集关键字参数
self.func(*self.args)
def test_1():
# 使用global关键字声明num,使得test_1作用域范围内可以使用num
global num
num = num + 1
print(num)
if __name__ == '__main__':
# num 是主线程的对象
num = 1
t1 = subThread(test_1)
t2 = subThread(test_1)
t1.start()
t1.join()
t2.start()
# stdout:
2
3
[Finished in 1.0s]
从以上示例可以看出,t2线程中的num对象是经过t1线程处理过的。
看看local对象实例化的效果:
from threading import Thread
from threading import local
#创建一个类,继承自Thread对象
class subThread(Thread):
#重载构造函数
def __init__(self,func,*args):
#调用基类的构造函数,必要的一步
Thread.__init__(self)
self.func = func
self.args = args
#重载Thread.run()方法
def run(self):
#带一个*号的参数,代表可以收集位置参数
#若是带两个*号的参数,代表可以收集关键字参数
self.func(*self.args)
def test_1():
# myData.num只属于一个线程。
myData.num = 1
myData.num = myData.num + 1
print(myData.num)
if __name__ == '__main__':
myData = local()
t1 = subThread(test_1)
t2 = subThread(test_1)
t1.start()
t1.join()
t2.start()
# stdout:
2
2
[Finished in 1.2s]
Tips:
- 如果在主线程中写myData.num=1,那么此时的myData.num只属于主线程。
- 如果子线程需要有私有数据,那么就在子线程内声明定义。
用法2:子类型
通过继承local对象来实现私有变量。
如下:
class My_Data(local):
def __init__(self):
self.num = 3
self.list = []
老规矩,先看看local不介入的场景:
from threading import Thread
from threading import local
#创建一个类,继承自Thread对象
class subThread(Thread):
#重载构造函数
def __init__(self,func,*args):
#调用基类的构造函数,必要的一步
Thread.__init__(self)
self.func = func
self.args = args
#重载Thread.run()方法
def run(self):
#带一个*号的参数,代表可以收集位置参数
#若是带两个*号的参数,代表可以收集关键字参数
self.func(*self.args)
class My_Data():
def __init__(self):
self.num = 3
self.list = []
def test_1():
myData.list.append(345)
print(myData.list)
if __name__ == '__main__':
myData = My_Data()
t1 = subThread(test_1)
t2 = subThread(test_1)
t1.start()
t1.join()
t2.start()
# stdout:
[345]
[345, 345]
[Finished in 1.2s]
依然,t2线程中的myData.list是在t1线程中处理过的。
local对象介入之后:
from threading import Thread
from threading import local
#创建一个类,继承自Thread对象
class subThread(Thread):
#重载构造函数
def __init__(self,func,*args):
#调用基类的构造函数,必要的一步
Thread.__init__(self)
self.func = func
self.args = args
#重载Thread.run()方法
def run(self):
#带一个*号的参数,代表可以收集位置参数
#若是带两个*号的参数,代表可以收集关键字参数
self.func(*self.args)
**# 与上例唯一的区别在此:**
class My_Data(local):
def __init__(self):
self.num = 3
self.list = []
def test_1():
myData.list.append(345)
print(myData.list)
if __name__ == '__main__':
myData = My_Data()
t1 = subThread(test_1)
t2 = subThread(test_1)
t1.start()
t1.join()
t2.start()
#stdout:
[345]
[345]
[Finished in 1.1s]
总结:
以上是local对象典型的用法。
- 可通过实例化local对象使用
- 可通过继承local对象实现子类来使用,此时,一般情况下应当在主线程中实例化子类,子线程中使用子类。
分享:
最后,分享一个笔者在实际工作中遇到的场景:
from threading import Thread
from threading import local
#创建一个类,继承自Thread对象
class subThread(Thread):
#重载构造函数
def __init__(self,func,*args):
#调用基类的构造函数,必要的一步
Thread.__init__(self)
self.func = func
self.args = args
#重载Thread.run()方法
def run(self):
#带一个*号的参数,代表可以收集位置参数
#若是带两个*号的参数,代表可以收集关键字参数
self.func(*self.args)
def test_1(a_list):
# 场景中a_list必须由调用函数传进来,也就是必须从主线程中传进来
# 但需要a_list对象是私有的,子线程各自对a_list的修改不能影响到其他线程
# 以下这种写法,是不能达到目的的
myData.list = a_list
myData.list.append(1)
print(myData.list)
if __name__ == '__main__':
myData = local()
a_list = []
t1 = subThread(test_1,a_list)
t2 = subThread(test_1,a_list)
t1.start()
t1.join()
t2.start()
# stdout:
[1]
[1, 1]
[Finished in 1.0s]
期望中t2线程中的a_list也应该是[1],而不是[1,1]。
这样才是正确的:
from threading import Thread
from threading import local
#创建一个类,继承自Thread对象
class subThread(Thread):
#重载构造函数
def __init__(self,func,*args):
#调用基类的构造函数,必要的一步
Thread.__init__(self)
self.func = func
self.args = args
#重载Thread.run()方法
def run(self):
#带一个*号的参数,代表可以收集位置参数
#若是带两个*号的参数,代表可以收集关键字参数
self.func(*self.args)
def test_1(a_list):
myData.list = a_list[:]
myData.list.append(1)
print(myData.list)
if __name__ == '__main__':
myData = local()
a_list = []
t1 = subThread(test_1,a_list)
t2 = subThread(test_1,a_list)
t1.start()
t1.join()
t2.start()
# stdout:
[1]
[1]
[Finished in 1.0s]
Tips:
在上述示例中,虽然a_list被代表着私有对象的Mydata.list引用,但其实不同的子线程都引用的是a_list对象引用的同一块内存。myData.list = a_list[:]能解决这种问题,通过类似深拷贝的方法。