學習筆記:
學習大佬的回答,從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屬性
參考: