您好,登錄后才能下訂單哦!
怎么深入理解Python中的ThreadLocal變量,相信很多沒有經驗的人對此束手無策,為此本文總結了問題出現的原因和解決方法,通過這篇文章希望你能解決這個問題。
在上篇我們看到了 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,用來提供更加友好的接口支持。
LocalStack
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__
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對象的代理,也非常值得去學習。
Python標準庫和Werkzeug在實現中都用到了很多python的黑魔法,不過最終提供給用戶的都是非常友好的接口。Werkzeug作為WSGI 工具集,為了解決Web開發中的特定使用問題,提供了一個改進版本,并且進行了一系列封裝,便于使用。不得不說,werkzeug的代碼可讀性非常好,注釋也是寫的非常棒,建議去閱讀源碼。
看完上述內容,你們掌握怎么深入理解Python中的ThreadLocal變量的方法了嗎?如果還想學到更多技能或想了解更多相關內容,歡迎關注億速云行業資訊頻道,感謝各位的閱讀!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。