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

資訊專欄INFORMATION COLUMN

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

DataPipeline / 837人閱讀

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

在 深入理解Python中的ThreadLocal變量(上) 中我們看到 ThreadLocal 的引入,使得可以很方便地在多線程環境中使用局部變量。如此美妙的功能到底是怎樣實現的?如果你對它的實現原理沒有好奇心或一探究竟的沖動,那么接下來的內容估計會讓你后悔自己的淺嘗輒止了。

簡單來說,Python 中 ThreadLocal 就是通過下圖中的方法,將全局變量偽裝成線程局部變量,相信讀完本篇文章你會理解圖中內容的。(對這張圖不眼熟的話,可以回顧下上篇))。

在哪里找到源碼?

好了,終于要來分析 ThreadLocal 是如何實現的啦,不過,等等,怎么找到它的源碼呢?上一篇中我們只是用過它(from threading import local),從這里只能看出它是在 threading 模塊實現的,那么如何找到 threading 模塊的源碼呢。

如果你在使用 PyCharm,恭喜你,你可以用 View source(OS X 快捷鍵是 ?↓)找到 local 定義的地方。現在許多 IDE 都有這個功能,可以查看 IDE 的幫助來找到該功能。接著我們就會發現 local 是這樣子的(這里以 python 2.7 為例):

# get thread-local implementation, either from the thread
# module, or from the python fallback
try:
    from thread import _local as local
except ImportError:
    from _threading_local import local

嗯,自帶解釋,非常好。我們要做的是繼續往下深挖具體實現,用同樣的方法(?↓)找 _local 的實現,好像不太妙,沒有找到純 python 實現:

class _local(object):
    """ Thread-local data """
    def __delattr__(self, name): # real signature unknown; restored from __doc__
        """ x.__delattr__("name") <==> del x.name """
        pass
    ...

沒關系,繼續來看下_threading_local吧,這下子終于找到了local的純 python 實現。開始就是很長的一段注釋文檔,告訴我們這個模塊是什么,如何用。這個文檔的質量非常高,值得我們去學習。所以,再次后悔自己的淺嘗輒止了吧,差點錯過了這么優秀的文檔范文!

將源碼私有化

在具體動手分析這個模塊之前,我們先把它拷出來放在一個多帶帶的文件 thread_local.py 中,這樣可以方便我們隨意肢解它(比如在適當的地方加上log),并用修改后的實現驗證我們的一些想法。此外,如果你真的理解了_threading_local.py最開始的一段,你就會發現這樣做是多么的有必要。因為python的threading.local不一定是用的_threading_local(還記得class _local(object) 嗎?)。

所以如果你用 threading.local 來驗證自己對_threading_local.py的理解,你很可能會一頭霧水的。不幸的是,我開始就這樣干的,所以被下面的代碼坑了好久:

from threading import local, current_thread
data = local()
key = object.__getattribute__(data, "_local__key") 
print current_thread().__dict__.get(key)
# AttributeError: "thread._local" object has no attribute "_local__key"

當然,你可能不理解這里是什么意思,沒關系,我只是想強調在 threading.local 沒有用到_threading_local.py,你必須要創建一個模塊(我將它命名為 thread_local.py)來保存_threading_local里面的內容,然后像下面這樣驗證自己的想法:

from threading import current_thread
from thread_local import local

data = local()
key = object.__getattribute__(data, "_local__key")
print current_thread().__dict__.get(key)
如何去理解源碼

現在可以靜下心來讀讀這不到兩百行的代碼了,不過,等等,好像有許多奇怪的內容(黑魔法):

__slots__

__new__

__getattribute__/__setattr__/__delattr__

Rlock

這些是什么?如果你不知道,沒關系,千萬不要被這些紙老虎嚇到,我們有豐富的文檔,查文檔就對了(這里不建議直接去網上搜相關關鍵字,最好是先讀文檔,讀完了有疑問再去搜)。

python 黑魔法

下面是我對上面提到的內容的一點總結,如果覺得讀的明白,那么可以繼續往下分析源碼了。如果還有不理解的,再讀幾遍文檔(或者我錯了,歡迎指出來)。

簡單來說,python 中創建一個新式類的實例時,首先會調用__new__(cls[, ...])創建實例,如果它成功返回cls類型的對象,然后才會調用__init__來對對象進行初始化。

新式類中我們可以用__slots__指定該類可以擁有的屬性名稱,這樣每個對象就不會再創建__dict__,從而節省對象占用的空間。特別需要注意的是,基類的__slots__并不會屏蔽派生類中__dict__的創建。

可以通過重載__setattr__,__delattr__和__getattribute__這些方法,來控制自定義類的屬性訪問(x.name),它們分別對應屬性的賦值,刪除,讀取。

鎖是操作系統中為了保證操作原子性而引入的概念,python 中 RLock是一種可重入鎖(reentrant lock,也可以叫作遞歸鎖),Rlock.acquire()可以不被阻塞地多次進入同一個線程。

__dict__用來保存對象的(可寫)屬性,可以是一個字典,或者其他映射對象。

源碼剖析

對這些相關的知識有了大概的了解后,再讀源碼就親切了很多。為了徹底理解,我們首先回想下平時是如何使用local對象的,然后分析源碼在背后的調用流程。這里從定義一個最簡單的thread-local對象開始,也就是說當我們寫下下面這句時,發生了什么?

data = local()

上面這句會調用 _localbase.__new__ 來為data對象設置一些屬性(還不知道有些屬性是做什么的,不要怕,后面遇見再說),然后將data的屬性字典(__dict__)作為當前線程的一個屬性值(這個屬性的 key 是根據 id(data) 生成的身份識別碼)。

這里很值得玩味:在創建ThreadLocal對象時,同時在線程(也是一個對象,沒錯萬物皆對象)的屬性字典__dict__里面保存了ThreadLocal對象的屬性字典。還記得文章開始的圖片嗎,紅色虛線就表示這個操作。

接著我們考慮在線程 Thread-1 中對ThreadLocal變量進行一些常用的操作,比如下面的一個操作序列:

data.name = "Thread 1(main)" # 調用 __setattr__
print data.name     # 調用 __getattribute__
del data.name       # 調用 __delattr__
print data.__dict__
# Thread 1(main)
# {}

那么背后又是如何操作的呢?上面的操作包括了給屬性賦值,讀屬性值,刪除屬性。這里我們以__getattribute__的實現為例(讀取值)進行分析,屬性的__setattr__和__delattr__和前者差不多,區別在于禁止了對__dict__屬性的更改以及刪除操作。

def __getattribute__(self, name):
    lock = object.__getattribute__(self, "_local__lock")
    lock.acquire()
    try:
        _patch(self)
        return object.__getattribute__(self, name)
    finally:
        lock.release()

函數中首先獲得了ThreadLocal變量的_local__lock屬性值(知道這個變量從哪里來的嗎,回顧下_localbase吧),然后用它來保證 _patch(self) 操作的原子性,還用 try-finally 保證即使拋出了異常也會釋放鎖資源,避免了線程意外情況下永久持有鎖而導致死鎖。現在問題是_patch究竟做了什么?答案還是在源碼中:

def _patch(self):
    key = object.__getattribute__(self, "_local__key")  # ThreadLocal變量 的標識符
    d = current_thread().__dict__.get(key)  # ThreadLocal變量在該線程下的數據
    if d is None:
        d = {}
        current_thread().__dict__[key] = d
        object.__setattr__(self, "__dict__", d)

        # we have a new instance dict, so call out __init__ if we have one
        cls = type(self)
        if cls.__init__ is not object.__init__:
            args, kw = object.__getattribute__(self, "_local__args")
            cls.__init__(self, *args, **kw)
    else:
        object.__setattr__(self, "__dict__", d)

_patch做的正是整個ThreadLocal實現中最核心的部分,從當前正在執行的線程對象那里拿到該線程的私有數據,然后將其交給ThreadLocal變量,就是本文開始圖片中的虛線2。這里需要補充說明以下幾點:

這里說的線程的私有數據,其實就是指通過x.name可以拿到的數據(其中 x 為ThreadLocal變量)

主線程中在創建ThreadLocal對象后,就有了對應的數據(還記得紅色虛線的意義嗎?)

對于那些第一次訪問ThreadLocal變量的線程來說,需要創建一個空的字典來保存私有數據,然后還要調用該變量的初始化函數。

還記得_localbase基類里__new__函數設置的屬性 _local__args 嗎?在這里被用來進行初始化。

到此,整個源碼核心部分已經理解的差不多了,只剩下local.__del__用來執行清除工作。因為每次創建一個ThreadLocal 變量,都會在進程對象的__dict__中添加相應的數據,當該變量被回收時,我們需要在相應的線程中刪除保存的對應數據。

從源碼中學到了什么?

經過一番努力,終于揭開了 ThreadLocal 的神秘面紗,整個過程可以說是收獲頗豐,下面一一說來。

不得不承認,計算機基礎知識很重要。你得知道進程、線程是什么,CPU 的工作機制,什么是操作的原子性,鎖是什么,為什么鎖使用不當會導致死鎖等等。

其次就是語言層面的知識也必不可少,就ThreadLocal的實現來說,如果對__new__,__slots__等不了解,根本不知道如何去做。所以,學語言還是要有深度,不然下面的代碼都看不懂:

class dict_test:
    pass

d = dict_test()
print d.__dict__
d.__dict__ = {"name": "Jack", "value": 12}
print d.name

還有就是高質量的功能實現需要考慮各方各面的因素,以ThreadLocal 為例,在基類_localbase中用__slots__節省空間,用try_finally保證異常環境也能正常釋放鎖,最后還用__del__來及時的清除無效的信息。

最后不得不說,好的文檔和注釋簡直就是畫龍點睛,不過寫文檔和注釋是門技術活,絕對需要不斷學習的

更多閱讀

Python"s use of __new__ and __init__?
Understanding __new__ and __init__
Usage of __slots__?
weakref – Garbage-collectable references to objects
How do I find the source code of a function in Python?
How do I find the location of Python module sources?
Is self.__dict__.update(**kwargs) good or poor style?
Doc: weakref — Weak references
python class 全面分析

我是如何閱讀開源項目的源代碼的
高效閱讀源代碼指南
如何閱讀程序源代碼?
如何看懂源代碼--(分析源代碼方法)

本文由selfboot 發表于個人博客,采用署名-非商業性使用-相同方式共享 3.0 中國大陸許可協議。
非商業轉載請注明作者及出處。商業轉載請聯系作者本人
本文標題為:深入理解Python中的ThreadLocal變量(中)
本文鏈接為:http://selfboot.cn/2016/08/26...

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/38122.html

相關文章

  • 深入理解PythonThreadLocal變量(上)

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

    huangjinnan 評論0 收藏0
  • 深入理解PythonThreadLocal變量(下)

    摘要:具體怎么實現的呢,思想其實特別簡單,我們在深入理解中的變量上一文的最后有提起過,就是創建一個全局字典,然后將線程或者協程標識符作為,相應線程或協程的局部數據作為。 在上篇我們看到了 ThreadLocal 變量的簡單使用,中篇對python中 ThreadLocal 的實現進行了分析,但故事還沒有結束。本篇我們一起來看下Werkzeug中ThreadLocal的設計。 Werkzeug...

    dadong 評論0 收藏0
  • Java 總結

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

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

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

    Maxiye 評論0 收藏0

發表評論

0條評論

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