threading.local()使用與原理剖析
- 2020 年 7 月 2 日
- 筆記
- Python並發編程
前言
還是第一次摘出某個方法來專門寫一篇隨筆,哈哈哈。
為什麼要寫這個方法呢?因為它確實太重要了,包括後期的Flask
框架源碼中都有它的影子。
那麼我們就來瞄一眼這個東西是啥吧。
作用
在Python官方中文文檔中(Python3.8.4),對它的介紹其實並不是很詳細
其實他的功能非常簡單,如下:
在一個全局的容器中可以存放一些線程獨有的數據,這些數據應是某一線程私有的,是除了本線程外的其他線程訪問不到的。
舉個例子,例如你用迅雷下載的時候每條線程的下載進度不一樣,我們需要將下載進度這個數據存儲起來,怎麼存儲?自己創建一個具有線程安全性的列表或字典?那太麻煩了,請記住一點,數據怎麼存不重要,關鍵是要方便取,取的快,取的准才是王道,所以直接用
thrading.local()
方法即可。
我來畫一張吧,靈魂畫師上線,能用圖描述絕對不打字。
基本使用
threading.local()
:可以實例化出一個寄存櫃,這個寄存櫃是全局化的
寄存櫃對象.你想放的東西名字 = 東西
:你可以在線程中這樣放入一個獨有的物件
寄存櫃對象.你放過的東西的名字
:這樣,你就可以將你存放進的東西拿出來。注意,只有你才能拿出來,其他人拿不了。
import threading import time def operate(name): """操作""" print("三個月後,{0}忽然想起了自己有件東西放在柜子里,決定取出來".format(name)) print("最後他從自己的柜子里取出來了:",Locker.article) def people(article): name = threading.current_thread().getName() # 獲取線程名 Locker.article = article # 這就表示將私有的的一件物品放在了寄存櫃里 print("{0}將{1}放在了寄存櫃里...".format(name,article)) time.sleep(3) # 過了三個月,這期間發生了很多事 operate(name) if __name__ == '__main__': Locker = threading.local() # 好了寄存櫃放在全局了 t1 = threading.Thread(target=people,args=("一封情書",),name="小王") t2 = threading.Thread(target=people,args=("一雙臭襪子",),name="小李") t1.start() t2.start() t1.join() t2.join() # ==== 執行結果 ==== """ 小王將一封情書放在了寄存櫃里... 小李將一雙臭襪子放在了寄存櫃里... 三個月後,小王忽然想起了自己有件東西放在柜子里,決定取出來 最後他從自己的柜子里取出來了: 一封情書 三個月後,小李忽然想起了自己有件東西放在柜子里,決定取出來 最後他從自己的柜子里取出來了: 一雙臭襪子 """
原理分析
我們可以自己做一個全局字典,來實現與這個類似的功能,但是使用起來肯定不太方便(它實際上本質就是字典嵌套進行存儲的):
import threading import time Locker = {} # 好了寄存櫃放在全局了 """ { 線程id:{"article":"存放的具體物品"}, 線程id:{"article":"存放的具體物品"}, 線程id:{"article":"存放的具體物品"}, } """ def operate(name): """操作""" print("三個月後,{0}忽然想起了自己有件東西放在柜子里,決定取出來".format(name)) ident = threading.get_ident() # 獲取線程ID print("最後他從自己的柜子里取出來了:",Locker[ident]["article"]) def people(article): name = threading.current_thread().getName() # 獲取線程名 ident = threading.get_ident() # 獲取線程ID Locker[ident] = {} # 創建了一個寄存櫃的小格子 Locker[ident]["article"] = article # 這就表示將私有的的一件物品放在了寄存櫃里 print("{0}將{1}放在了寄存櫃里...".format(name,article)) time.sleep(3) # 過了三個月,這期間發生了很多事 operate(name) if __name__ == '__main__': t1 = threading.Thread(target=people,args=("一封情書",),name="小王") t2 = threading.Thread(target=people,args=("一雙臭襪子",),name="小李") t1.start() t2.start() t1.join() t2.join() # ==== 執行結果 ==== """ 小王將一封情書放在了寄存櫃里... 小李將一雙臭襪子放在了寄存櫃里... 三個月後,小李忽然想起了自己有件東西放在柜子里,決定取出來 最後他從自己的柜子里取出來了: 一雙臭襪子 三個月後,小王忽然想起了自己有件東西放在柜子里,決定取出來 最後他從自己的柜子里取出來了: 一封情書 """
嘗試自己做出一個寄存櫃
這樣做是不是太麻煩了?我們可以自定義一個類,讓它變得更加簡單,如同原本的使用方法一樣。其實下面代碼中採取的方法也是threading.local()
所採取的方法。
import threading import time class MyLocker(object): cabinet = {} # 柜子 """ { 線程id:{"article":"存放的具體物品"}, 線程id:{"article":"存放的具體物品"}, 線程id:{"article":"存放的具體物品"}, } """ def __getattr__(self, item): """當訪問屬性不存在時觸發""" ident = threading.get_ident() return MyLocker.cabinet[ident][item] def __setattr__(self, key, value): """試圖用 . 去設置屬性時觸發""" ident = threading.get_ident() if ident in MyLocker.cabinet: # 如果在柜子里這重新賦值 MyLocker.cabinet[ident][key] = value else: MyLocker.cabinet[ident] = {key: value} # 如果不在則做一個小格子,並且把物件存放進來 def operate(name): """操作""" print("三個月後,{0}忽然想起了自己有件東西放在柜子里,決定取出來".format(name)) print("最後他從自己的柜子里取出來了:", Locker.article) def people(article): name = threading.current_thread().getName() # 獲取線程名 Locker.article = article # 這就表示將私有的的一件物品放在了寄存櫃里 print("{0}將{1}放在了寄存櫃里...".format(name, article)) time.sleep(3) # 過了三個月,這期間發生了很多事 operate(name) if __name__ == '__main__': Locker = MyLocker() # 好了寄存櫃放在全局了 t1 = threading.Thread(target=people, args=("一封情書",), name="小王") t2 = threading.Thread(target=people, args=("一雙臭襪子",), name="小李") t1.start() t2.start() t1.join() t2.join() # ==== 執行結果 ==== """ 小王將一封情書放在了寄存櫃里... 小李將一雙臭襪子放在了寄存櫃里... 三個月後,小王忽然想起了自己有件東西放在柜子里,決定取出來 最後他從自己的柜子里取出來了: 一封情書 三個月後,小李忽然想起了自己有件東西放在柜子里,決定取出來 最後他從自己的柜子里取出來了: 一雙臭襪子 """