学习笔记:
学习大佬的回答,从Flask源码分析使用context stacks的原因。结论如下:
1. Flask的每个应用之间都是进程隔离的,在不同的wsgi工具下(例如gunicorn),一个worker进程中可能有多个线程(或协程)用于并发处理多个请求。在处理一个request时,需要获取当前线程的app,request等变量。考虑到线程隔离,就需要有一个线程隔离的对象来保存这些全局变量。
2. 为什么获取appapp,request等请求相关的变量要使用代理方式?
通常使用Local的方式来存储线程隔离的变量,为了方便管理,在一个线程中最好只维护一个Local对象,把不同的变量都存储在这个对象中即可。因此,为了方便管理,不同的变量只需要通过代理的方式,去获取当前线程中的Local,并从中获取需要的变量,而不用每个变量都要去管理Local对象。
2. 为什么使用栈格式?
为了兼容一个请求中需要多个request的情况,比如内部重定向时(资料很少,即通常不这样使用)就需要在一个request栈中压入多个对象。这里考虑旧的request你还不能丢,因为请求没有结束。
下文为答案摘录:
Local
Local实现线程隔离的对象存储,你可以在一个进程中拥有多个线程隔离的Local对象,也就可以在一个进程中存储多个request,g,current app和其他类似的对象。
简化后的代码如下:
class Local(object)
def __init__(self):
self.storage = {}
def __getattr__(self, name):
context_id = get_ident() # we get the current thread's or greenlet's id
contextual_storage = self.storage.setdefault(context_id, {})
try:
return contextual_storage[name]
except KeyError:
raise AttributeError(name)
def __setattr__(self, name, value):
context_id = get_ident()
contextual_storage = self.storage.setdefault(context_id, {})
contextual_storage[name] = value
def __release_local__(self):
context_id = get_ident()
self.storage.pop(context_id, None)
local = Local()
代码中可以看到最重要的方法是get_ident(),它可以标识当前的线程或协程。Local使用这个标识来区分不同线程。
LocalProxy
但是Flask中并没有直接使用Local对象来获取上述提到的对象,而是用LocalProxy(代理)。
LocalProxy通过查询当前线程中的Local对象来查找其中的对象。
初始化代理的时候:
# this would happen early during processing of an http request
local.request = RequestContext(http_environment)
local.g = SomeGeneralPurposeContainer()
初始化应用的时候:
# this would happen some time near application start-up
local = Local()
request = LocalProxy(local, 'request')
g = LocalProxy(local, 'g')
优点:
1. 简化了对这些对象的管理,只需要一个单独的Local对象,就可以通过不同类型的代理对象去获取想要的值。请求结束后也只需要释放一个Local对象即可,不需要管理各种代理对象。
LocalStack
Flask中也没有直接只用LocalProxy,因为我们知道Flask在一个请求中可能会包含多个request(比如内部重定向),一个进程里也会处理多个应用上下文。虽然这些情况很少发生,但是Flask框架需要兼容这种情况,所以引入栈(LocalStack)的方式。
注意:LocalStack指Local里存储栈,栈里面保存多个线程隔离对象,通过代理方式访问。
class LocalStack(object):
def __init__(self):
self.local = Local()
def push(self, obj):
"""Pushes a new item to the stack"""
rv = getattr(self.local, 'stack', None)
if rv is None:
self.local.stack = rv = []
rv.append(obj)
return rv
def pop(self):
"""Removes the topmost item from the stack, will return the
old value or `None` if the stack was already empty.
"""
stack = getattr(self.local, 'stack', None)
if stack is None:
return None
elif len(stack) == 1:
release_local(self.local) # this simply releases the local
return stack[-1]
else:
return stack.pop()
@property
def top(self):
"""The topmost item on the stack. If the stack is empty,
`None` is returned.
"""
try:
return self.local.stack[-1]
except (AttributeError, IndexError):
return None
一个视图的请求初始化完成后,查找请求路径(request.path)的流程如下:
- 从全局可访问LocalProxy对象request开始。
- 为了找到其对应的对象(代理的对象),它将调用其查找函数_find_request()。
- 该函数查询LocalStack对象_request_ctx_stack的栈顶的上下文对象。
- 为了找到顶部的上下文对象,LocalStack对象首先在其Local属性(self.local)中查询先前存储在此处的stack属性。
- 从stack中获得栈顶的上下文
- top.request就这样作为当前的request对象。
- 从request对象我们就可以得到path属性
参考: