成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

你知道神奇的弱引用嗎

開發 前端
因為 Data 一旦被創建后,就保存在緩存字典中,永遠都不會釋放!換句話講,程序的資源比如內存,會不斷地增長,最終很有可能會爆掉。因此,我們希望一個數據等所有線程都不再訪問后,能夠自動釋放。

 [[427639]]

本文轉載自微信公眾號「小菜學編程」,作者fasionchan。轉載本文請聯系小菜學編程公眾號。

背景

開始討論弱引用( weakref )之前,我們先來看看什么是弱引用?它到底有什么作用?

假設我們有一個多線程程序,并發處理應用數據:

  1. # 占用大量資源,創建銷毀成本很高 
  2. class Data: 
  3.     def __init__(self, key): 
  4.         pass 

應用數據 Data 由一個 key 唯一標識,同一個數據可能被多個線程同時訪問。由于 Data 需要占用很多系統資源,創建和消費的成本很高。我們希望 Data 在程序中只維護一個副本,就算被多個線程同時訪問,也不想重復創建。

為此,我們嘗試設計一個緩存中間件 Cacher :

  1. import threading 
  2. # 數據緩存 
  3. class Cacher: 
  4.     def __init__(self): 
  5.         self.pool = {} 
  6.         self.lock = threading.Lock() 
  7.     def get(self, key): 
  8.         with self.lock: 
  9.             data = self.pool.get(key
  10.             if data: 
  11.                 return data 
  12.             self.pool[key] = data = Data(key
  13.             return data 

Cacher 內部用一個 dict 對象來緩存已創建的 Data 副本,并提供 get 方法用于獲取應用數據 Data 。get 方法獲取數據時先查緩存字典,如果數據已存在,便直接將其返回;如果數據不存在,則創建一個并保存到字典中。因此,數據首次被創建后就進入緩存字典,后續如有其它線程同時訪問,使用的都是緩存中的同一個副本。

感覺非常不錯!但美中不足的是:Cacher 有資源泄露的風險!

因為 Data 一旦被創建后,就保存在緩存字典中,永遠都不會釋放!換句話講,程序的資源比如內存,會不斷地增長,最終很有可能會爆掉。因此,我們希望一個數據等所有線程都不再訪問后,能夠自動釋放。

我們可以在 Cacher 中維護數據的引用次數, get 方法自動累加這個計數。于此同時提供一個 remove 新方法用于釋放數據,它先自減引用次數,并在引用次數降為零時將數據從緩存字段中刪除。

線程調用 get 方法獲取數據,數據用完后需要調用 remove 方法將其釋放。Cacher 相當于自己也實現了一遍引用計數法,這也太麻煩了吧!Python 不是內置了垃圾回收機制嗎?為什么應用程序還需要自行實現呢?

沖突的主要癥結在于 Cacher 的緩存字典:它作為一個中間件,本身并不使用數據對象,因此理論上不應該對數據產生引用。那有什么黑科技能夠在不產生引用的前提下,找到目標對象嗎?我們知道,賦值都是會產生引用的!

典型用法

這時,弱引用( weakref )隆重登場了!弱引用是一種特殊的對象,能夠在不產生引用的前提下,關聯目標對象。

  1. # 創建一個數據 
  2. >>> d = Data('fasionchan.com'
  3. >>> d 
  4. <__main__.Data object at 0x1018571f0> 
  5.  
  6. # 創建一個指向該數據的弱引用 
  7. >>> import weakref 
  8. >>> r = weakref.ref(d) 
  9.  
  10. # 調用弱引用對象,即可找到指向的對象 
  11. >>> r() 
  12. <__main__.Data object at 0x1018571f0> 
  13. >>> r() is d 
  14. True 
  15.  
  16. # 刪除臨時變量d,Data對象就沒有其他引用了,它將被回收 
  17. >>> del d 
  18. # 再次調用弱引用對象,發現目標Data對象已經不在了(返回None) 
  19. >>> r() 

這樣一來,我們只需將 Cacher 緩存字典改成保存弱引用,問題便迎刃而解!

  1. import threading 
  2. import weakref 
  3. # 數據緩存 
  4. class Cacher: 
  5.     def __init__(self): 
  6.         self.pool = {} 
  7.         self.lock = threading.Lock() 
  8.     def get(self, key): 
  9.         with self.lock: 
  10.             r = self.pool.get(key
  11.             if r: 
  12.                 data = r() 
  13.                 if data: 
  14.                     return data 
  15.             data = Data(key
  16.             self.pool[key] = weakref.ref(data) 
  17.             return data 

由于緩存字典只保存 Data 對象的弱引用,因此 Cacher 不會影響 Data 對象的引用計數。當所有線程都用完數據后,引用計數就降為零因而被釋放。

實際上,用字典緩存數據對象的做法很常用,為此 weakref 模塊還提供了兩種只保存弱引用的字典對象:

  • weakref.WeakKeyDictionary ,鍵只保存弱引用的映射類(一旦鍵不再有強引用,鍵值對條目將自動消失);
  • weakref.WeakValueDictionary ,值只保存弱引用的映射類(一旦值不再有強引用,鍵值對條目將自動消失);

因此,我們的數據緩存字典可以采用 weakref.WeakValueDictionary 來實現,它的接口跟普通字典完全一樣。這樣我們不用再自行維護弱引用對象,代碼邏輯更加簡潔明了:

  1. import threading 
  2. import weakref 
  3. # 數據緩存 
  4. class Cacher: 
  5.     def __init__(self): 
  6.         self.pool = weakref.WeakValueDictionary() 
  7.         self.lock = threading.Lock() 
  8.     def get(self, key): 
  9.         with self.lock: 
  10.             data = self.pool.get(key
  11.             if data: 
  12.                 return data 
  13.             self.pool[key] = data = Data(key
  14.             return data 

weakref 模塊還有很多好用的工具類和工具函數,具體細節請參考官方文檔,這里不再贅述。

工作原理

那么,弱引用到底是何方神圣,為什么會有如此神奇的魔力呢?接下來,我們一起揭下它的面紗,一睹真容!

  1. >>> d = Data('fasionchan.com'
  2.  
  3. # weakref.ref 是一個內置類型對象 
  4. >>> from weakref import ref 
  5. >>> ref 
  6. <class 'weakref'
  7.  
  8. # 調用weakref.ref類型對象,創建了一個弱引用實例對象 
  9. >>> r = ref(d) 
  10. >>> r 
  11. <weakref at 0x1008d5b80; to 'Data' at 0x100873d60> 

經過前面章節,我們對閱讀內建對象源碼已經輕車熟路了,相關源碼文件如下:

  • Include/weakrefobject.h 頭文件包含對象結構體和一些宏定義;
  • Objects/weakrefobject.c 源文件包含弱引用類型對象及其方法定義;

我們先扒一扒弱引用對象的字段結構,定義于 Include/weakrefobject.h 頭文件中的第 10-41 行:

  1. typedef struct _PyWeakReference PyWeakReference; 
  2.  
  3. /* PyWeakReference is the base struct for the Python ReferenceType, ProxyType, 
  4.  * and CallableProxyType. 
  5.  */ 
  6. #ifndef Py_LIMITED_API 
  7. struct _PyWeakReference { 
  8.     PyObject_HEAD 
  9.  
  10.     /* The object to which this is a weak reference, or Py_None if none. 
  11.      * Note that this is a stealth reference:  wr_object's refcount is 
  12.      * not incremented to reflect this pointer. 
  13.      */ 
  14.     PyObject *wr_object; 
  15.  
  16.     /* A callable to invoke when wr_object dies, or NULL if none. */ 
  17.     PyObject *wr_callback; 
  18.  
  19.     /* A cache for wr_object's hash code.  As usual for hashes, this is -1 
  20.      * if the hash code isn't known yet. 
  21.      */ 
  22.     Py_hash_t hash; 
  23.  
  24.     /* If wr_object is weakly referenced, wr_object has a doubly-linked NULL
  25.      * terminated list of weak references to it.  These are the list pointers. 
  26.      * If wr_object goes away, wr_object is set to Py_None, and these pointers 
  27.      * have no meaning then
  28.      */ 
  29.     PyWeakReference *wr_prev; 
  30.     PyWeakReference *wr_next; 
  31. }; 
  32. #endif 

由此可見,PyWeakReference 結構體便是弱引用對象的肉身。它是一個定長對象,除固定頭部外還有 5 個字段:

  • wr_object ,對象指針,指向被引用對象,弱引用根據該字段可以找到被引用對象,但不會產生引用;
  • wr_callback ,指向一個可調用對象,當被引用的對象銷毀時將被調用;
  • hash ,緩存被引用對象的哈希值;
  • wr_prev 和 wr_next 分別是前后向指針,用于將弱引用對象組織成雙向鏈表;

結合代碼中的注釋,我們知道:

  • 弱引用對象通過 wr_object 字段關聯被引用的對象,如上圖虛線箭頭所示;
  • 一個對象可以同時被多個弱引用對象關聯,圖中的 Data 實例對象被兩個弱引用對象關聯;
  • 所有關聯同一個對象的弱引用,被組織成一個雙向鏈表,鏈表頭保存在被引用對象中,如上圖實線箭頭所示;
  • 當一個對象被銷毀后,Python 將遍歷它的弱引用鏈表,逐一處理:

將 wr_object 字段設為 None ,弱引用對象再被調用將返回 None ,調用者便知道對象已經被銷毀了;

執行回調函數 wr_callback (如有);

由此可見,弱引用的工作原理其實就是設計模式中的 觀察者模式( Observer )。當對象被銷毀,它的所有弱引用對象都得到通知,并被妥善處理。

實現細節

掌握弱引用的基本原理,足以讓我們將其用好。如果您對源碼感興趣,還可以再深入研究它的一些實現細節。

前面我們提到,對同一對象的所有弱引用,被組織成一個雙向鏈表,鏈表頭保存在對象中。由于能夠創建弱引用的對象類型是多種多樣的,很難由一個固定的結構體來表示。因此,Python 在類型對象中提供一個字段 tp_weaklistoffset ,記錄弱引用鏈表頭指針在實例對象中的偏移量。

由此一來,對于任意對象 o ,我們只需通過 ob_type 字段找到它的類型對象 t ,再根據 t 中的 tp_weaklistoffset 字段即可找到對象 o 的弱引用鏈表頭。

Python 在 Include/objimpl.h 頭文件中提供了兩個宏定義:

  1. /* Test if a type supports weak references */ 
  2. #define PyType_SUPPORTS_WEAKREFS(t) ((t)->tp_weaklistoffset > 0) 
  3.  
  4. #define PyObject_GET_WEAKREFS_LISTPTR(o) \ 
  5.     ((PyObject **) (((char *) (o)) + Py_TYPE(o)->tp_weaklistoffset)) 
  • PyType_SUPPORTS_WEAKREFS 用于判斷類型對象是否支持弱引用,僅當 tp_weaklistoffset 大于零才支持弱引用,內置對象 list 等都不支持弱引用;
  • PyObject_GET_WEAKREFS_LISTPTR 用于取出一個對象的弱引用鏈表頭,它先通過 Py_TYPE 宏找到類型對象 t ,再找通過 tp_weaklistoffset 字段確定偏移量,最后與對象地址相加即可得到鏈表頭字段的地址;

我們創建弱引用時,需要調用弱引用類型對象 weakref 并將被引用對象 d 作為參數傳進去。弱引用類型對象 weakref 是所有弱引用實例對象的類型,是一個全局唯一的類型對象,定義在 Objects/weakrefobject.c 中,即:_PyWeakref_RefType(第 350 行)。

根據對象模型中學到的知識,Python 調用一個對象時,執行的是其類型對象中的 tp_call 函數。因此,調用弱引用類型對象 weakref 時,執行的是 weakref 的類型對象,也就是 type 的 tp_call 函數。tp_call 函數則回過頭來調用 weakref 的 tp_new 和 tp_init 函數,其中 tp_new 為實例對象分配內存,而 tp_init 則負責初始化實例對象。

回到 Objects/weakrefobject.c 源文件,可以看到 _PyWeakref_RefType 的 tp_new 字段被初始化成 weakref___new__ (第 276 行)。該函數的主要處理邏輯如下:

  1. 解析參數,得到被引用的對象(第 282 行);
  2. 調用 PyType_SUPPORTS_WEAKREFS 宏判斷被引用的對象是否支持弱引用,不支持就拋異常(第 286 行);
  3. 調用 GET_WEAKREFS_LISTPTR 行取出對象的弱引用鏈表頭字段,為方便插入返回的是一個二級指針(第 294 行);
  4. 調用 get_basic_refs 取出鏈表最前那個 callback 為空 基礎弱引用對象(如有,第 295 行);
  5. 如果 callback 為空,而且對象存在 callback 為空的基礎弱引用,則復用該實例直接將其返回(第 296 行);
  6. 如果不能復用,調用 tp_alloc 函數分配內存、完成字段初始化,并插到對象的弱引用鏈表(第 309 行);
  • 如果 callback 為空,直接將其插入到鏈表最前面,方便后續復用(見第 4 點);
  • 如果 callback 非空,將其插到基礎弱引用對象(如有)之后,保證基礎弱引用位于鏈表頭,方便獲取;

當一個對象被回收后,tp_dealloc 函數將調用 PyObject_ClearWeakRefs 函數對它的弱引用進行清理。該函數取出對象的弱引用鏈表,然后逐個遍歷,清理 wr_object 字段并執行 wr_callback 回調函數(如有)。具體細節不再展開,有興趣的話可以自行查閱 Objects/weakrefobject.c 中的源碼,位于 880 行。

好了,經過本節學習,我們徹底掌握了弱引用相關知識。弱引用可以在不產生引用計數的前提下,對目標對象進行管理,常用于框架和中間件中。弱引用看起來很神奇,其實設計原理是非常簡單的觀察者模式。弱引用對象創建后便插到一個由目標對象維護的鏈表中,觀察(訂閱)對象的銷毀事件。

 

責任編輯:武曉燕 來源: 小菜學編程
相關推薦

2021-12-09 15:45:09

Python弱引用代碼

2024-10-09 08:54:31

2020-04-03 11:24:50

LinuxUnix進程

2023-01-31 09:02:24

JSVMVR

2022-06-01 07:10:43

遞歸字典極限

2013-08-19 17:14:04

.Net強引用弱引用

2010-11-23 10:21:53

跳槽

2024-03-08 13:33:08

PG數據安全

2022-06-29 08:32:04

游標MySQL服務器

2019-06-03 10:14:07

API網關微服務

2018-01-10 08:27:00

2022-09-22 14:55:31

前端JavaScripthis

2022-09-26 13:10:17

JavaScriptthis

2023-12-12 08:41:01

2022-10-24 09:57:02

runeGo語言

2020-12-02 09:01:40

Java基礎

2021-11-25 07:42:11

命令Linux系統

2016-11-23 08:36:38

Windows 10登錄PIN碼快

2011-08-23 13:50:17

程序員

2015-11-02 17:20:00

Java弱引用
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 亚洲人成人一区二区在线观看 | 亚洲欧美另类在线观看 | 中日字幕大片在线播放 | 91av免费看| 亚洲欧美一区二区三区国产精品 | 久久久久国产精品午夜一区 | 成人国产午夜在线观看 | 国产一区二区三区四区三区四 | 精品日韩在线观看 | 色婷婷一区 | 久草热在线 | 2020国产在线 | 久久亚洲欧美日韩精品专区 | 国产免费观看久久黄av片涩av | 国产一区久久 | 中文字幕在线免费视频 | 一区二区三区视频在线 | 久久久久久美女 | 精品国模一区二区三区欧美 | 欧美精品乱码99久久影院 | 国产高潮好爽受不了了夜色 | 欧美精品在线一区 | 亚洲www啪成人一区二区 | 欧美日韩精品一区二区三区蜜桃 | 日韩视频专区 | 午夜成人免费视频 | 五月婷婷丁香 | 一区二区三区视频在线免费观看 | 国产精品无码久久久久 | 蜜臀网| 国产三级精品视频 | 丁香婷婷久久久综合精品国产 | 午夜精品一区二区三区免费视频 | 午夜av影院 | 亚洲资源在线 | 精品av天堂毛片久久久借种 | 不卡的av电影 | h在线播放 | 国产精品高潮呻吟久久 | 97精品视频在线 | 欧美一区二区三区视频 |