国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

深入理解Python中的ThreadLocal變量(下)

dadong / 3454人閱讀

摘要:具體怎么實現的呢,思想其實特別簡單,我們在深入理解中的變量上一文的最后有提起過,就是創建一個全局字典,然后將線程或者協程標識符作為,相應線程或協程的局部數據作為。

在上篇我們看到了 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對象的代理,也非常值得去學習。

通過這三篇文章,相信對 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變量(中)

    摘要:在深入理解中的變量上中我們看到的引入,使得可以很方便地在多線程環境中使用局部變量。特別需要注意的是,基類的并不會屏蔽派生類中的創建。到此,整個源碼核心部分已經理解的差不多了,只剩下用來執行清除工作。 在 深入理解Python中的ThreadLocal變量(上) 中我們看到 ThreadLocal 的引入,使得可以很方便地在多線程環境中使用局部變量。如此美妙的功能到底是怎樣實現的?如果你...

    DataPipeline 評論0 收藏0
  • 深入理解Python中的ThreadLocal變量(上)

    摘要:我們知道多線程環境下,每一個線程均可以使用所屬進程的全局變量。在線程中使用局部變量則不存在這個問題,因為每個線程的局部變量不能被其他線程訪問。 我們知道多線程環境下,每一個線程均可以使用所屬進程的全局變量。如果一個線程對全局變量進行了修改,將會影響到其他所有的線程。為了避免多個線程同時對變量進行修改,引入了線程同步機制,通過互斥鎖,條件變量或者讀寫鎖來控制對全局變量的訪問。 只用全局變...

    huangjinnan 評論0 收藏0
  • Java 總結

    摘要:中的詳解必修個多線程問題總結個多線程問題總結有哪些源代碼看了后讓你收獲很多,代碼思維和能力有較大的提升有哪些源代碼看了后讓你收獲很多,代碼思維和能力有較大的提升開源的運行原理從虛擬機工作流程看運行原理。 自己實現集合框架 (三): 單鏈表的實現 自己實現集合框架 (三): 單鏈表的實現 基于 POI 封裝 ExcelUtil 精簡的 Excel 導入導出 由于 poi 本身只是針對于 ...

    caspar 評論0 收藏0
  • Java面試題必備知識之ThreadLocal

    摘要:方法,刪除當前線程綁定的這個副本數字,這個值是的值,普通的是使用鏈表來處理沖突的,但是是使用線性探測法來處理沖突的,就是每次增加的步長,根據參考資料所說,選擇這個數字是為了讓沖突概率最小。 showImg(https://segmentfault.com/img/remote/1460000019828633); 老套路,先列舉下關于ThreadLocal常見的疑問,希望可以通過這篇學...

    Maxiye 評論0 收藏0

發表評論

0條評論

dadong

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<