在上篇咱們看到了 ThreadLocal 變量的簡單使用,中篇對python中 ThreadLocal 的實現進行了分析,但故事尚未結束。本篇咱們一塊兒來看下Werkzeug中ThreadLocal的設計。html
Werkzeug 做爲一個 WSGI 工具庫,因爲一些方面的考慮,並無直接使用python內置的ThreadLocal類,而是本身實現了一系列Local類。包括簡單的Local,以及在此基礎上實現的LocalStack,LocalManager 和 LocalProxy。接下來咱們一塊兒來看看這些類的使用方式,設計的初衷,以及具體的實現技巧。python
Werkzeug 的設計者認爲python自帶的ThreadLocal並不能知足需求,主要由於下面兩個緣由:git
Werkzeug 主要用「ThreadLocal」來知足併發的要求,python 自帶的ThreadLocal只能實現基於線程的併發。而python中還有其餘許多併發方式,好比常見的協程(greenlet),所以須要實現一種可以支持協程的Local對象。github
WSGI不保證每次都會產生一個新的線程來處理請求,也就是說線程是能夠複用的(能夠維護一個線程池來處理請求)。這樣若是werkzeug 使用python自帶的ThreadLocal,一個「不乾淨(存有以前處理過的請求的相關數據)」的線程會被用來處理新的請求。flask
爲了解決這兩個問題,werkzeug 中實現了Local類。Local對象能夠作到線程和協程之間數據的隔離,此外,還要支持清理某個線程或者協程下的數據(這樣就能夠在處理一個請求以後,清理相應的數據,而後等待下一個請求的到來)。併發
具體怎麼實現的呢,思想其實特別簡單,咱們在深刻理解Python中的ThreadLocal變量(上) 一文的最後有提起過,就是建立一個全局字典,而後將線程(或者協程)標識符做爲key,相應線程(或協程)的局部數據做爲 value。這裏 werkzeug 就是按照上面思路進行實現,不過利用了python的一些黑魔法,最後提供給用戶一個清晰、簡單的接口。app
Local 類的實如今 werkzeug.local 中,以 8a84b62 版本的代碼進行分析。經過前兩篇對ThreadLocal的瞭解,咱們已經知道了Local對象的特色和使用方法。因此這裏再也不給出Local對象的使用例子,咱們直接看代碼。ide
class Local(object): __slots__ = ('__storage__', '__ident_func__') def __init__(self): object.__setattr__(self, '__storage__', {}) object.__setattr__(self, '__ident_func__', get_ident) ...
因爲可能有大量的Local對象,爲了節省Local對象佔用的空間,這裏使用 __slots__
寫死了Local能夠擁有的屬性:函數
__storage__: 值爲一個字典,用來保存實際的數據,初始化爲空;工具
__ident_func__:值爲一個函數,用來找到當前線程或者協程的標誌符。
因爲Local對象實際的數據保存在__storage__中,因此對Local屬性的操做實際上是對__storage__的操做。對於獲取屬性而言,這裏用魔術方法__getattr__
攔截__storage__ 和 __ident_func__之外的屬性獲取,將其導向__storage__存儲的當前線程或協程的數據。而對於屬性值的set或者del,則分別用__setattr__和__setattr__來實現(這些魔術方法的介紹見屬性控制)。關鍵代碼以下所示:
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)
假設咱們有ID爲1,2, ... , N 的N個線程或者協程,每一個都用Local對象保存有本身的一些局部數據,那麼Local對象的內容以下圖所示:
此外,Local類還提供了__release_local__
方法,用來釋放當前線程或者協程保存的數據。
Werkzeug 在 Local 的基礎上實現了 LocalStack 和 LocalManager,用來提供更加友好的接口支持。
LocalStack經過封裝Local從而實現了一個線程(或者協程)獨立的棧結構,註釋裏面有具體的使用方法,一個簡單的使用例子以下:
ls = LocalStack() ls.push(12) print ls.top # 12 print ls._local.__storage__ # {140735190843392: {'stack': [12]}}
LocalStack 的實現比較有意思,它將一個Local對象做爲本身的屬性_local
,而後定義接口push, pop 和 top 方法進行相應的棧操做。這裏用 _local.__storage__._local.__ident_func__() 這個list來模擬棧結構。在接口push, pop和top中,經過操做這個list來模擬棧的操做,須要注意的是在接口函數內部獲取這個list時,不用像上面黑體那麼複雜,能夠直接用_local的getattr()方法便可。以 push 函數爲例,實現以下:
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
pop 和 top 的實現和通常棧相似,都是對 stack = getattr(self._local, 'stack', None)
這個列表進行相應的操做。此外,LocalStack還容許咱們自定義__ident_func__
,這裏用 內置函數 property 生成了描述器,封裝了__ident_func__的get和set操做,提供了一個屬性值__ident_func__做爲接口,具體代碼以下:
def _get__ident_func__(self): return self._local.__ident_func__ def _set__ident_func__(self, value): object.__setattr__(self._local, '__ident_func__', value) __ident_func__ = property(_get__ident_func__, _set__ident_func__) del _get__ident_func__, _set__ident_func__
Local 和 LocalStack 都是線程或者協程獨立的單個對象,不少時候咱們須要一個線程或者協程獨立的容器,來組織多個Local或者LocalStack對象(就像咱們用一個list來組織多個int或者string類型同樣)。
Werkzeug實現了LocalManager,它經過一個list類型的屬性locals來存儲所管理的Local或者LocalStack對象,還提供cleanup方法來釋放全部的Local對象。Werkzeug中LocalManager最主要的接口就是裝飾器方法make_middleware
,代碼以下:
def make_middleware(self, app): """Wrap a WSGI application so that cleaning up happens after request end. """ def application(environ, start_response): return ClosingIterator(app(environ, start_response), self.cleanup) return application
這個裝飾器註冊了回調函數cleanup,當一個線程(或者協程)處理完請求以後,就會調用cleanup清理它所管理的Local或者LocalStack 對象(ClosingIterator 的實如今 werkzeug.wsgi中)。下面是一個使用 LocalManager 的簡單例子:
from werkzeug.local import Local, LocalManager local = Local() local_2 = Local() local_manager = LocalManager([local, local2]) def application(environ, start_response): local.request = request = Request(environ) ... # application 處理完畢後,會自動清理local_manager 的內容 application = local_manager.make_middleware(application)
經過LocalManager的make_middleware咱們能夠在某個線程(協程)處理完一個請求後,清空全部的Local或者LocalStack對象,這樣這個線程又能夠處理另外一個請求了。至此,文章開始時提到的第二個問題就能夠解決了。Werkzeug.local 裏面還實現了一個 LocalProxy 用來做爲Local對象的代理,也很是值得去學習。
經過這三篇文章,相信對 ThreadLocal 有了一個初步的瞭解。Python標準庫和Werkzeug在實現中都用到了不少python的黑魔法,不過最終提供給用戶的都是很是友好的接口。Werkzeug做爲WSGI 工具集,爲了解決Web開發中的特定使用問題,提供了一個改進版本,而且進行了一系列封裝,便於使用。不得不說,werkzeug的代碼可讀性很是好,註釋也是寫的很是棒,建議去閱讀源碼。
本文由selfboot 發表於我的博客,採用署名-非商業性使用-相同方式共享 3.0 中國大陸許可協議。
非商業轉載請註明做者及出處。商業轉載請聯繫做者本人
本文標題爲:深刻理解Python中的ThreadLocal變量(下)
本文連接爲:http://selfboot.cn/2016/11/03...
Context Locals
Private Variables and Class-local References
How does the @property decorator work?
How do the Proxy, Decorator, Adapter, and Bridge Patterns differ?
Flask源碼剖析
Werkzeug.locals 模塊解讀
Charming Python: 從Flask的request提及
How to remove a key from a python dictionary?
werkzeug源碼分析(local.py)