预热
面向对象
class Local(object):
def __init__(self):
self.dict = {}
def __setattr__(self, key, value):
self.dict[key] = value
obj = Local()
这样写会报错
如果自定义了自己的__setattr__,在初始化的时候就不能这样给对象绑定属性,因为使用self.dict = {}的时候就会去调用__setattr__去给对象设置属性,但是此时对象还没有一个dict的属性。但凡是给自己写的
这个类实例化的对象,调用对象的obj.xx = oo 这种方式都会触发__setattr__的执行,但是如果在这个对象里操作一个字典对象(或其他对象),对这个字典对象(或其他对象)的操作就不会有问题,因为就算其他对象的.
方式
是调用其他对象自己的__setattr__,和我们写的类没有关系
ThreadLocal
from threading import get_ident
from threading import Thread
import time
class Local(object):
pass
obj = Local()
def task(i):
obj.x = i
time.sleep(1)
print(get_ident(),':',obj.x)
if __name__ == '__main__':
for i in range(10):
t = Thread(target=task, args=(i,))
t.start()
得到的结果如下
这是因为在python中的多线程中,对同一个全局变量的修改会有竞争问题,此时全局变量obj最终存的值是最后一个线程赋的值。要想实现每个线程的数据隔离,单独的线程打印单独线程传入的变量,可以在每个线程维护相对应的局部变量,代码如下
from threading import get_ident
from threading import Thread
import time
class Local(object):
pass
obj = Local()
def task(i):
dic = {'i':i}
time.sleep(1)
print(get_ident(),':',dic['i'])
if __name__ == '__main__':
for i in range(10):
t = Thread(target=task, args=(i,))
t.start()
此时的结果为
但是python中还提供了一个叫做线程局部变量的玩意,叫local.
from threading import local
from threading import get_ident
from threading import Thread
import time
obj = local()
def task(i):
obj.x = i
time.sleep(1)
print(get_ident(),':',obj.x)
if __name__ == '__main__':
for i in range(10):
t = Thread(target=task, args=(i,))
t.start()
此时的结果就是
从上面的结果不难看出,为什么local叫线程局部变量,因为local对象明明是定义在全局的,但是却有类似于在线程创建相应的局部变量的效果。
从使用效果上来看,这个叫做线程局部变量的玩意好牛逼,能让我们以很简单类似于平常使用类的方式做到了数据隔离。是的,乍一看很牛逼,说白了就是别人在内部做了一些事,封装的好。那么内部做了些啥事呢,可以这样先简单地去理解:这个对象内部维护一个大字典,字典的key就是线程的唯一标识,value也是一个字典,这个字典存线程的数据。通过这个想法,自定义一个支持协程的隔离数据的类
try:
from gevent import getcurrent as get_ident
except ImportError:
from threading import get_ident
from threading import Thread
import time
class Local(object):
def __init__(self):
object.__setattr__(self,'__storage__', {})
def __setattr__(self, key, value):
thread_id = get_ident()
try:
self.__storage__[thread_id][key] = value
except KeyError as e:
self.__storage__[thread_id] = {key: value}
def __getattr__(self, item):
# __getattr__ 是在obj.属性的时候被调用,而且这个属性必须不是对象本身的属性
# 这里__setattr__ 并不是把属性绑定到对象,而是放到字典里,所以即使obj.x = 1,访问obj.x 还是会调用__getattr__
thread_id = get_ident()
try:
return self.__storage__[thread_id][item]
except KeyError:
return None
obj = Local()
def task(i):
obj.x = i
time.sleep(1)
print(get_ident(),':',obj.x)
if __name__ == '__main__':
for i in range(10):
t = Thread(target=task, args=(i,))
t.start()