Python線程之認識線程安全
一、什麼是線程安全?
線程安全,名字就非常直接,在多線程情況下是安全的,多線程操作上的安全。
比如一個計算加法的函數,不管是一千個還是一萬個線程,我們希望它執行的結果總是正確的,1+1 必須永遠等於2, 而不是線程少的時候1+1 變成3或者4瞭。
通常我們都用線程安全來修飾一個類,修飾一個函數:
我們會說我設計的這個類是線程安全的
這意味著,在多線程環境下,同時調用這個類的函數不會出現函數設置預期之外的異常(上述的1+1=3的情況)
二、在Python中有哪些類是線程安全的?
dict 和 list,tuple這些都是線程安全。
它們是被全局解釋器保障瞭,這個鎖:GIL(全局解釋器鎖)確保瞭任何時候隻能有一個線程執行相應操作的字節碼。參考
但是這番話也是說的不清不楚的。
現在我們拿轉賬來解析吧:
xuewei_account = dict() xuewei_account['amount'] = 100 # amount為負數即是轉出金額 def transfer(money): xuewei_account['amount'] += money
如上,代碼為一個函數對jb_account
(賬戶)進行轉入金額操作。
這裡用瞭dict類型,GIL會保證隻有一個線程操作賬戶。
下面是多個線程進行操作的代碼:
import random import threading import datetime import time xuewei_account = dict() xuewei_account['amount'] = 100 # amount為負數即是轉出金額 def transfer(money): xuewei_account['amount'] += money # 創建4個任務給重復學委賬戶轉賬 threads = [] for i in range(200): t1 = threading.Thread(target=lambda: transfer(-1)) threads.append(t1) t2 = threading.Thread(target=lambda: transfer(1)) threads.append(t2) for t in threads: t.start() # 這次不用sleep瞭,用join來等待所有線程執行完畢 # join函數必須線程start後才能調用,否則出錯。 for t in threads: t.join() print("-" * 16) print("活躍線程數:", threading.active_count()) print("活躍線程:", threading.current_thread().name) print("學委賬戶餘額:", xuewei_account)
這段代碼運行的輸出結果正常,因為是反復+1/-1,最後肯定是恢復原賬戶餘額。
雖然多個線程,但是每個線程隻對xuewei_account進行一次讀寫,這時候dict是安全的。
但是我們把賦值修改dict的操作變多之後(特別是一個線程內反復多次獲取值然後修改),像下面的代碼:
import random import threading import datetime import time xuewei_account = dict() xuewei_account['amount'] = 100 # amount為負數即是轉出金額 def transfer(money): for i in range(100000): xuewei_account['amount'] = xuewei_account['amount'] + money # 創建400個任務重復給學委賬戶轉賬 threads = [] for i in range(200): t1 = threading.Thread(target=lambda: transfer(-1)) threads.append(t1) t2 = threading.Thread(target=lambda: transfer(1)) threads.append(t2) for t in threads: t.start() for t in threads: t.join() print("-" * 16) print("活躍線程數:", threading.active_count()) print("活躍線程:", threading.current_thread().name) print("學委賬戶餘額:", xuewei_account)
這是某一次運行結果(不保證每次acount的數值一樣):
我們看到dict還是扛不住多個線程反復的寫操作。
這裡區別是:每個線程隻對xuewei_account進行大量讀寫,雖然dict是安全的,但是多個線程中間穿插修改瞭account,程序方法棧出現操作到舊值(看下面的圖)。
主要是下面這段代碼:
xuewei_account[‘amount'] += money # 即是 xuewei_account[‘amount'] = xuewei_account[‘amount']+ money
再一步抽象簡化可以寫成:
a = a + b
每個線程都執行 +b 操作,最後a的值應該是a+2b。
上面的操作意味這下面的情況發生瞭:
在某個線程中可能出現某一個線程T1獲取瞭a值 ,準備加上b。
另外一個線程T2已經完成瞭a+b操作,把a的值變成瞭a+b瞭。
但是接下來T1 拿瞭a的值再執行a+b操作,把a的值變成a+b。
這樣就少加瞭一個b,本來最後結果是a+2b 的變成瞭 a+b(因為T1拿瞭a的舊值,中間T2執行完,T1才繼續執行)
當然實際多線程之間交互比上圖還要隨機。
三、如何做到真正線程安全?
dict讀取數據是線程安全,但是被反復讀寫就容易出現數據混亂。
如果我們要設計一個線程安全的函數,那麼它必須不涉及任何共享變量或者是完全沒有狀態依賴的函數
def thread_safe_method(): pass
1.無狀態函數
比如下面的加法函數,不管多少個線程調用,返回值永遠是預期的a+b。
def add(a, b): return a + b
2.另一種 化繁為簡
許我們可以把多線程轉換為單線程,這個需要一個線程安全的媒介。
到此這篇關於Python線程之認識線程安全 的文章就介紹到這瞭,更多相關認識Python線程安全 內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- Python的線程使用隊列Queue來改造轉賬場景
- Python線程之如何解決共享變量問題
- Python線程之線程安全的隊列Queue
- Python線程編程之Thread詳解
- Python多線程即相關理念詳解