- 什么是线程隔离
简单的说,就是将用户请求线程和服务执行线程分割开来,同时约定了每个服务最多可用线程数。
1.Local(线程隔离对象)
class Local(object):
__slots__ = ("__storage__", "__ident_func__")
def __init__(self):
object.__setattr__(self, "__storage__", {})
object.__setattr__(self, "__ident_func__", get_ident)
def __iter__(self):
return iter(self.__storage__.items())
def __call__(self, proxy):
"""Create a proxy for a name."""
return LocalProxy(self, proxy)
def __release_local__(self):
self.__storage__.pop(self.__ident_func__(), None)
def __getattr__(self, name):
try:
return self.__storage__[self.__ident_func__()][name]
except KeyError:
raise AttributeError(name)
def __setattr__(self, name, value):
ident = self.__ident_func__()
storage = self.__storage__
try:
storage[ident][name] = value
except KeyError:
storage[ident] = {name: value}
def __delattr__(self, name):
try:
del self.__storage__[self.__ident_func__()][name]
except KeyError:
raise AttributeError(name)
Local 是werkzeug.local下的一个类,也是实现线程隔离的核心。
简述一下local类会干什么
- 首先在实例化的时候回创建一个字典,格式为
storage={}
- 接着看到_setattr__方法中,每次在设置值的时候,会获取当前线程的ID,要存的k-v
- _setattr__成功了一个,结构会变成
storage={
ID(当前线程id):{name(k):value(v)}
}
- 如此storage会变成一个双层的字典,第一层字典会以线程ID作为key值,如此一来,每个线程都会拥有自己的一个数据空间(storage的第二层字典)。Local就是通过双层字典的方式实现的线程隔离。
Local_test
import threading
import time
from werkzeug.local import LocalStack,Local
obj_local = Local()
obj_local.a = 1
def modiftData():
obj_local.a = 2
print('new thread a: {}'.format(obj_local.a))
if __name__ == '__main__':
# 新建一个子线程执行
new_thread = threading.Thread(target=modiftData)
new_thread.start()
time.sleep(2)
print('main thread a: {}'.format(obj_local.a))
结果验证确实做到了线程隔离的作用,不过这样看有点不够,不明白到底是怎么运行的,下面就详细讲解它的过程。
- 主线程赋值过程
如图,将线程Id作为了key值存到了storage里,并且将a=1存进了线程key里。
进入异常是因为字典赋值的时候,想寻找到第二个key是,不能找到第一个key的位置。(不是重点) - 新线程赋值
赋值流程与主线程是一样的,不过最后可以看到,storage是同时存了两个线程的。也验证了使用storage作为双层字典,ID(线程)为第一层的key实现了线程隔离。
ps:不要随便改源码,出了问题很麻烦,这里为了展示添加的占位符
2.LocalStack(线程隔离栈)
LocalStack实现线程隔离,是通过对Local进行封装。主要是内部实例化了一个Local对象,这是LocalStack的核心
源码太长,给个图好了
然后为了实现栈应有的功能,定义了两个方法(push,pop)和一个属性(top)
storage={
ID:{
'stack':[]
}
}
LocalStack的数据结构本质就是在local二级字典里,添加了一个key为stack,value为List的k-v。
然后对外只提供对stack的append和pop方法,让它实现栈应有的功能。
3.为什么要使用线程隔离
- 如图是访问了两次的/hello的api,打印出的不同的结果,可以看出no_local这个类的实例,两次打出的数据是不一样的。造成的原因是我定义的这个类本身并没有做线程隔离的操作。
- Flask里请求会有一个Request的类来生成一个实例,被request请求上下文封装,然后request又被LocalStack存储。所以实现了线程隔离。
- 如此,线程隔离可以确保每次请求的数据都是一样的,不会因为请求的先后造成数据污染。