多线程threading之私有变量


前言

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对象典型的用法。

  1. 可通过实例化local对象使用
  2. 可通过继承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[:]能解决这种问题,通过类似深拷贝的方法。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值