local简介
- 在
local
模块中,Werkzeug实现了类似Python标准库中thread.local
的功能。 thread.local
是线程局部变量,也就是每个线程的私有变量,具有线程隔离性,可以通过线程安全的方式获取或者改变线程中的变量。- 为什么不用python的内部库
thread.local
?- 在WSGI中会使用协程 , 而协程会复用线程。
- 所以不能保证每个请求都有自己的线程,可能是请求正在重用以前的线程。
- 一个线程中存在多个请求,用
thread.local
变量处理起来会造成多个请求间数据的相互干扰。
local
模块实现了四个类
Local
LocalStack
LocalProxy
LocalManager
Local类
Local
类能够用来存储线程/协程的私有变量。在功能上这个thread.local
类似。与之不同的是,
Local
类支持Python的协程。在Werkzeug库的local模块中,Local
类实现了一种数据结构,用来保存线程的私有变量对于其具体形式,可以参考它的构造函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29class Local(object):
__slots__ = ('__storage__', '__ident_func__')
def __init__(self):
object.__setattr__(self, '__storage__', {})
object.__setattr__(self, '__ident_func__', get_ident)
# 迭代Local的时候 , 返回的是{20212: {'arg0': 0 , 'arg1': 1},}这样的字典格式
def __iter__(self):
return iter(self.__storage__.items())
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
类具有两个属性:__storage__
: 一个字典,用来存储不同的线程/协程 , 以及这些线程/协程中的变量。其数据结构为 :
1
2
3
4
5{
20212: {'arg0': 0 , 'arg1': 1},
20404: {'arg1': 1},
21512: {'arg2': 2}
}__ident_func__
: 一个函数,用来识别当前线程或协程。
__ident_func__
- 用于识别当前线程或协程,
local
模块引入get_ident
函数。- 如果支持协程,则从
greenlet
库中导入相关函数, - 否则从
thread
库中导入相关函数。
- 如果支持协程,则从
- 调用
get_ident
将返回一个整数,这个整数可以确定当前线程或者协程。
1 | # # 在有greenlet的情况下,get_indent实际获取的是greenlet的id,而没有greenlet的情况下获取的是thread id |
__storage__
__storage__
是一个字典,用来存储不同的线程/协程,以及这些线程/协程中的变量。- 以下是一个简单的多线程的例子,用来说明
__storage__
的具体结构。
1 | import threading |
__storage__
这个字典的键表示不同的线程(通过get_ident
函数获得线程标识数值),而值表示对应线程中的变量。- 这种结构将不同的线程分离开来。当某个线程要访问该线程的变量时,便可以通过
get_ident
函数获得线程标识数值,进而可以在字典中获得该键对应的值信息了。
LocalStack类
LocalStack
类和Local
类类似,区别在于其数据结构是栈,而Local是字典的形式。Local对象存储的时候是类似字典的方式,需要有key和value,而LocalStack是基于栈的,通过push和pop来存储和弹出数据。
另外,当我们想释放存储空间的时候,也可以调用release_local()。
请求上下文
和应用上下文
的实现都是基于LocalStack。
LocalStack的_local属性是一个Local对象。我们的属性的键被定为stack,值是一个列表。通过这个列表实现了栈的push和pop。
1 | class LocalStack(object): |
- 在
LocalStack
类初始化的时候,便会创建一个Local
实例,这个实例用于存储线程/协程的变量。 - 与此同时,
LocalStack
类还实现了push
、pop
、top
等方法或属性。调用这些属性或者方法时,该类会根据当前线程或协程的标识数值,在Local
实例中对相应的数值进行操作。 - 以下还是以一个多线程的例子进行说明:
1 | from werkzeug.local import LocalStack, LocalProxy |
以上例子的具体分析过程如下:
- 首先,先创建一个LocalStack实例_stack,这个实例将存储线程/协程的变量信息
- 在程序开始运行时,先检查_stack中包含的信息;
- 之后创建两个线程,分别执行worker函数;
- worker函数首先会产生一个上下文对象,这个上下文对象会放入_stack中。在这个上下文环境中,程序执行一些操作,打印一些数据。当退出上下文环境时,_stack会pop该上下文对象。
- 在程序结束时,再次检查_stack中包含的信息。
结果如下:
1 | (MainThread) Stack Initial State: [] |
注意到:
- 当两个线程在运行时,
_stack
中会存储这两个线程的信息,每个线程的信息都保存在类似{'stack': [a1, b1, c1]}
的结构中(注:stack键对应的是放入该栈中的对象,此处为了方便打印了该对象的一些属性)。 - 当线程在休眠和运行中切换时,通过线程的标识数值进行区分不同线程,线程1运行时它通过标识数值只会对属于该线程的数值进行操作,而不会和线程2的数值混淆,这样便起到线程隔离的效果(而不是通过锁的方式)。
- 由于是在一个上下文环境中运行,当线程执行完毕时,
_stack
会将该线程存储的信息删除掉。在上面的运行结果中可以看出,当线程2运行结束后,_stack
中只包含线程1的相关信息。当所有线程都运行结束,_stack
的最终状态将为空。
Local类和LocalStack类总结:
1 | local = Local() |