自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

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

開發(fā) 前端
Python 中 ThreadLocal 就是通過下圖中的方法,將全局變量偽裝成線程局部變量,相信讀完本篇文章你會理解圖中內(nèi)容的。

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

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

 

在哪里找到源碼?

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

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

  1. # get thread-local implementation, either from the thread 
  2. # module, or from the python fallback 
  3. try: 
  4.     from thread import _local as local 
  5. except ImportError: 
  6.     from _threading_local import local  

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

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

沒關(guān)系,繼續(xù)來看下_threading_local吧,這下子終于找到了local的純 python 實現(xiàn)。開始就是很長的一段注釋文檔,告訴我們這個模塊是什么,如何用。這個文檔的質(zhì)量非常高,值得我們?nèi)W(xué)習(xí)。所以,再次后悔自己的淺嘗輒止了吧,差點錯過了這么優(yōu)秀的文檔范文!

將源碼私有化

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

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

  1. from threading import local, current_thread 
  2. data = local() 
  3. key = object.__getattribute__(data, '_local__key')  
  4. print current_thread().__dict__.get(key
  5. # AttributeError: 'thread._local' object has no attribute '_local__key'  

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

  1. from threading import current_thread 
  2. from thread_local import local 
  3.  
  4. data = local() 
  5. key = object.__getattribute__(data, '_local__key'
  6. print current_thread().__dict__.get(key 

如何去理解源碼

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

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

python 黑魔法

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

  • 簡單來說,python 中創(chuàng)建一個新式類的實例時,首先會調(diào)用__new__(cls[, ...])創(chuàng)建實例,如果它成功返回cls類型的對象,然后才會調(diào)用__init__來對對象進(jìn)行初始化。
  • 新式類中我們可以用__slots__指定該類可以擁有的屬性名稱,這樣每個對象就不會再創(chuàng)建__dict__,從而節(jié)省對象占用的空間。特別需要注意的是,基類的__slots__并不會屏蔽派生類中__dict__的創(chuàng)建。
  • 可以通過重載__setattr__,__delattr__和__getattribute__這些方法,來控制自定義類的屬性訪問(x.name),它們分別對應(yīng)屬性的賦值,刪除,讀取。
  • 鎖是操作系統(tǒng)中為了保證操作原子性而引入的概念,python 中 RLock是一種可重入鎖(reentrant lock,也可以叫作遞歸鎖),Rlock.acquire()可以不被阻塞地多次進(jìn)入同一個線程。
  • __dict__用來保存對象的(可寫)屬性,可以是一個字典,或者其他映射對象。

源碼剖析

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

  1. data = local() 

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

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

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

  1. data.name = "Thread 1(main)" # 調(diào)用 __setattr__ 
  2. print data.name     # 調(diào)用 __getattribute__ 
  3. del data.name       # 調(diào)用 __delattr__ 
  4. print data.__dict__ 
  5. # Thread 1(main) 
  6. # {}  

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

  1. def __getattribute__(self, name): 
  2.     lock = object.__getattribute__(self, '_local__lock'
  3.     lock.acquire() 
  4.     try: 
  5.         _patch(self) 
  6.         return object.__getattribute__(self, name
  7.     finally: 
  8.         lock.release()  

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

  1. def _patch(self): 
  2.     key = object.__getattribute__(self, '_local__key')  # ThreadLocal變量 的標(biāo)識符 
  3.     d = current_thread().__dict__.get(key)  # ThreadLocal變量在該線程下的數(shù)據(jù) 
  4.     if d is None: 
  5.         d = {} 
  6.         current_thread().__dict__[key] = d 
  7.         object.__setattr__(self, '__dict__', d) 
  8.  
  9.         # we have a new instance dict, so call out __init__ if we have one 
  10.         cls = type(self) 
  11.         if cls.__init__ is not object.__init__: 
  12.             args, kw = object.__getattribute__(self, '_local__args'
  13.             cls.__init__(self, *args, **kw) 
  14.     else
  15.         object.__setattr__(self, '__dict__', d)  

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

  • 這里說的線程的私有數(shù)據(jù),其實就是指通過x.name可以拿到的數(shù)據(jù)(其中 x 為ThreadLocal變量)
  • 主線程中在創(chuàng)建ThreadLocal對象后,就有了對應(yīng)的數(shù)據(jù)(還記得紅色虛線的意義嗎?)
  • 對于那些第一次訪問ThreadLocal變量的線程來說,需要創(chuàng)建一個空的字典來保存私有數(shù)據(jù),然后還要調(diào)用該變量的初始化函數(shù)。
  • 還記得_localbase基類里__new__函數(shù)設(shè)置的屬性 _local__args 嗎?在這里被用來進(jìn)行初始化。

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

從源碼中學(xué)到了什么?

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

不得不承認(rèn),計算機(jī)基礎(chǔ)知識很重要。你得知道進(jìn)程、線程是什么,CPU 的工作機(jī)制,什么是操作的原子性,鎖是什么,為什么鎖使用不當(dāng)會導(dǎo)致死鎖等等。

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

  1. class dict_test: 
  2.     pass 
  3.  
  4. d = dict_test() 
  5. print d.__dict__ 
  6. d.__dict__ = {'name''Jack''value': 12} 
  7. print d.name  

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

最后不得不說,好的文檔和注釋簡直就是畫龍點睛,不過寫文檔和注釋是門技術(shù)活,絕對需要不斷學(xué)習(xí)的。

責(zé)任編輯:龐桂玉 來源: segmentfault
相關(guān)推薦

2016-08-31 15:41:19

PythonThreadLoca變量

2016-11-07 21:59:52

threadpython

2021-05-13 21:27:24

ThreadLocal多線程多線程并發(fā)安全

2020-12-11 07:32:45

編程ThreadLocalJava

2024-07-18 10:12:04

2020-12-26 16:51:12

Python操作符開發(fā)

2018-07-09 15:11:14

Java逃逸JVM

2020-12-16 09:47:01

JavaScript箭頭函數(shù)開發(fā)

2023-10-08 08:53:36

數(shù)據(jù)庫MySQL算法

2010-06-28 10:12:01

PHP匿名函數(shù)

2014-06-23 10:42:56

iOS開發(fā)UIScrollVie

2013-11-05 13:29:04

JavaScriptreplace

2013-06-20 10:25:56

2022-02-14 07:47:26

overlayfsdockerrootfs

2022-03-25 09:01:16

CSS溢出屬性

2025-03-06 08:20:00

RAG嵌入模型

2023-10-27 11:27:14

Go函數(shù)

2023-03-02 08:26:36

RedisAVL紅黑樹

2023-10-31 10:51:56

MySQLMVCC并發(fā)性

2015-12-28 11:25:51

C++異常處理機(jī)制
點贊
收藏

51CTO技術(shù)棧公眾號