Flask的context stacks有什麼用?

學習筆記:

學習大佬的回答,從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屬性

參考:

https://stackoverflow.com/questions/20036520/what-is-the-purpose-of-flasks-context-stacks/20041823#20041823

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章