Python中的ThreadLocal變數

高洛峰
發布: 2016-11-08 09:54:23
原創
1339 人瀏覽過

Werkzeug 作為一個 WSGI 工具庫,由於一些方面的考慮,並沒有直接使用python內建的ThreadLocal類,而是自己實作了一系列Local類。包括簡單的Local,以及在此基礎上實現的LocalStack,LocalManager 和 LocalProxy。接下來我們一起來看看這些類別的使用方式,設計的初衷,以及具體的實作技巧。

Local 類的設計

Werkzeug 的設計者認為python自帶的ThreadLocal並不能滿足需求,主要因為下面兩個原因:

Werkzeug 主要用「ThreadLocal」來滿足並發的要求,python 附帶的Threadthal能實現基於線程的並發。而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物件的內容如下圖所示:

Python中的ThreadLocal變數

此外,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 的内容
登入後複製

通过LocalManager的make_middleware我们可以在某个线程(协程)处理完一个请求后,清空所有的Local或者LocalStack对象,这样这个线程又可以处理另一个请求了。至此,文章开始时提到的第二个问题就可以解决了。Werkzeug.local 里面还实现了一个 LocalProxy 用来作为Local对象的代理,也非常值得去学习。

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!