摘要:具體怎么實現的呢,思想其實特別簡單,我們在深入理解中的變量上一文的最后有提起過,就是創建一個全局字典,然后將線程或者協程標識符作為,相應線程或協程的局部數據作為。
在上篇我們看到了 ThreadLocal 變量的簡單使用,中篇對python中 ThreadLocal 的實現進行了分析,但故事還沒有結束。本篇我們一起來看下Werkzeug中ThreadLocal的設計。
Werkzeug 作為一個 WSGI 工具庫,由于一些方面的考慮,并沒有直接使用python內置的ThreadLocal類,而是自己實現了一系列Local類。包括簡單的Local,以及在此基礎上實現的LocalStack,LocalManager 和 LocalProxy。接下來我們一起來看看這些類的使用方式,設計的初衷,以及具體的實現技巧。
Local 類的設計Werkzeug 的設計者認為python自帶的ThreadLocal并不能滿足需求,主要因為下面兩個原因:
Werkzeug 主要用“ThreadLocal”來滿足并發的要求,python 自帶的ThreadLocal只能實現基于線程的并發。而python中還有其他許多并發方式,比如常見的協程(greenlet),因此需要實現一種能夠支持協程的Local對象。
WSGI不保證每次都會產生一個新的線程來處理請求,也就是說線程是可以復用的(可以維護一個線程池來處理請求)。這樣如果werkzeug 使用python自帶的ThreadLocal,一個“不干凈(存有之前處理過的請求的相關數據)”的線程會被用來處理新的請求。
為了解決這兩個問題,werkzeug 中實現了Local類。Local對象可以做到線程和協程之間數據的隔離,此外,還要支持清理某個線程或者協程下的數據(這樣就可以在處理一個請求之后,清理相應的數據,然后等待下一個請求的到來)。
具體怎么實現的呢,思想其實特別簡單,我們在深入理解Python中的ThreadLocal變量(上) 一文的最后有提起過,就是創建一個全局字典,然后將線程(或者協程)標識符作為key,相應線程(或協程)的局部數據作為 value。這里 werkzeug 就是按照上面思路進行實現,不過利用了python的一些黑魔法,最后提供給用戶一個清晰、簡單的接口。
具體實現Local 類的實現在 werkzeug.local 中,以 8a84b62 版本的代碼進行分析。通過前兩篇對ThreadLocal的了解,我們已經知道了Local對象的特點和使用方法。所以這里不再給出Local對象的使用例子,我們直接看代碼。
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__方法,用來釋放當前線程或者協程保存的數據。
Local 擴展接口Werkzeug 在 Local 的基礎上實現了 LocalStack 和 LocalManager,用來提供更加友好的接口支持。
LocalStackLocalStack通過封裝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__LocalManager
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)
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/44220.html
摘要:在深入理解中的變量上中我們看到的引入,使得可以很方便地在多線程環境中使用局部變量。特別需要注意的是,基類的并不會屏蔽派生類中的創建。到此,整個源碼核心部分已經理解的差不多了,只剩下用來執行清除工作。 在 深入理解Python中的ThreadLocal變量(上) 中我們看到 ThreadLocal 的引入,使得可以很方便地在多線程環境中使用局部變量。如此美妙的功能到底是怎樣實現的?如果你...
摘要:我們知道多線程環境下,每一個線程均可以使用所屬進程的全局變量。在線程中使用局部變量則不存在這個問題,因為每個線程的局部變量不能被其他線程訪問。 我們知道多線程環境下,每一個線程均可以使用所屬進程的全局變量。如果一個線程對全局變量進行了修改,將會影響到其他所有的線程。為了避免多個線程同時對變量進行修改,引入了線程同步機制,通過互斥鎖,條件變量或者讀寫鎖來控制對全局變量的訪問。 只用全局變...
摘要:方法,刪除當前線程綁定的這個副本數字,這個值是的值,普通的是使用鏈表來處理沖突的,但是是使用線性探測法來處理沖突的,就是每次增加的步長,根據參考資料所說,選擇這個數字是為了讓沖突概率最小。 showImg(https://segmentfault.com/img/remote/1460000019828633); 老套路,先列舉下關于ThreadLocal常見的疑問,希望可以通過這篇學...
閱讀 1655·2021-09-26 09:55
閱讀 5248·2021-09-22 15:40
閱讀 2013·2019-08-30 15:53
閱讀 1497·2019-08-30 11:15
閱讀 1714·2019-08-29 15:41
閱讀 1869·2019-08-28 18:13
閱讀 3146·2019-08-26 12:00
閱讀 1668·2019-08-26 10:30