您好,登錄后才能下訂單哦!
這篇文章主要介紹“Python線程安全實(shí)例分析”的相關(guān)知識(shí),小編通過(guò)實(shí)際案例向大家展示操作過(guò)程,操作方法簡(jiǎn)單快捷,實(shí)用性強(qiáng),希望這篇“Python線程安全實(shí)例分析”文章能幫助大家解決問(wèn)題。
線程安全,名字就非常直接,在多線程情況下是安全的,多線程操作上的安全。
比如一個(gè)計(jì)算加法的函數(shù),不管是一千個(gè)還是一萬(wàn)個(gè)線程,我們希望它執(zhí)行的結(jié)果總是正確的,1+1 必須永遠(yuǎn)等于2, 而不是線程少的時(shí)候1+1 變成3或者4了。
通常我們都用線程安全來(lái)修飾一個(gè)類(lèi),修飾一個(gè)函數(shù):
我們會(huì)說(shuō)我設(shè)計(jì)的這個(gè)類(lèi)是線程安全的
這意味著,在多線程環(huán)境下,同時(shí)調(diào)用這個(gè)類(lèi)的函數(shù)不會(huì)出現(xiàn)函數(shù)設(shè)置預(yù)期之外的異常(上述的1+1=3的情況)
dict 和 list,tuple這些都是線程安全。
它們是被全局解釋器保障了,這個(gè)鎖:GIL(全局解釋器鎖)確保了任何時(shí)候只能有一個(gè)線程執(zhí)行相應(yīng)操作的字節(jié)碼。參考
但是這番話也是說(shuō)的不清不楚的。
現(xiàn)在我們拿轉(zhuǎn)賬來(lái)解析吧:
xuewei_account = dict() xuewei_account['amount'] = 100 # amount為負(fù)數(shù)即是轉(zhuǎn)出金額 def transfer(money): xuewei_account['amount'] += money
如上,代碼為一個(gè)函數(shù)對(duì)jb_account
(賬戶)進(jìn)行轉(zhuǎn)入金額操作。
這里用了dict類(lèi)型,GIL會(huì)保證只有一個(gè)線程操作賬戶。
下面是多個(gè)線程進(jìn)行操作的代碼:
import random import threading import datetime import time xuewei_account = dict() xuewei_account['amount'] = 100 # amount為負(fù)數(shù)即是轉(zhuǎn)出金額 def transfer(money): xuewei_account['amount'] += money # 創(chuàng)建4個(gè)任務(wù)給重復(fù)學(xué)委賬戶轉(zhuǎn)賬 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來(lái)等待所有線程執(zhí)行完畢 # join函數(shù)必須線程start后才能調(diào)用,否則出錯(cuò)。 for t in threads: t.join() print("-" * 16) print("活躍線程數(shù):", threading.active_count()) print("活躍線程:", threading.current_thread().name) print("學(xué)委賬戶余額:", xuewei_account)
這段代碼運(yùn)行的輸出結(jié)果正常,因?yàn)槭欠磸?fù)+1/-1,最后肯定是恢復(fù)原賬戶余額。
雖然多個(gè)線程,但是每個(gè)線程只對(duì)xuewei_account進(jìn)行一次讀寫(xiě),這時(shí)候dict是安全的。
但是我們把賦值修改dict的操作變多之后(特別是一個(gè)線程內(nèi)反復(fù)多次獲取值然后修改),像下面的代碼:
import random import threading import datetime import time xuewei_account = dict() xuewei_account['amount'] = 100 # amount為負(fù)數(shù)即是轉(zhuǎn)出金額 def transfer(money): for i in range(100000): xuewei_account['amount'] = xuewei_account['amount'] + money # 創(chuàng)建400個(gè)任務(wù)重復(fù)給學(xué)委賬戶轉(zhuǎn)賬 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("活躍線程數(shù):", threading.active_count()) print("活躍線程:", threading.current_thread().name) print("學(xué)委賬戶余額:", xuewei_account)
這是某一次運(yùn)行結(jié)果(不保證每次acount的數(shù)值一樣):
我們看到dict還是扛不住多個(gè)線程反復(fù)的寫(xiě)操作。
這里區(qū)別是:每個(gè)線程只對(duì)xuewei_account進(jìn)行大量讀寫(xiě),雖然dict是安全的,但是多個(gè)線程中間穿插修改了account,程序方法棧出現(xiàn)操作到舊值(看下面的圖)。
主要是下面這段代碼:
xuewei_account[‘a(chǎn)mount'] += money # 即是 xuewei_account[‘a(chǎn)mount'] = xuewei_account[‘a(chǎn)mount']+ money
再一步抽象簡(jiǎn)化可以寫(xiě)成:
a = a + b
每個(gè)線程都執(zhí)行 +b 操作,最后a的值應(yīng)該是a+2b。
上面的操作意味這下面的情況發(fā)生了:
在某個(gè)線程中可能出現(xiàn)某一個(gè)線程T1獲取了a值 ,準(zhǔn)備加上b。
另外一個(gè)線程T2已經(jīng)完成了a+b操作,把a(bǔ)的值變成了a+b了。
但是接下來(lái)T1 拿了a的值再執(zhí)行a+b操作,把a(bǔ)的值變成a+b。
這樣就少加了一個(gè)b,本來(lái)最后結(jié)果是a+2b 的變成了 a+b(因?yàn)門(mén)1拿了a的舊值,中間T2執(zhí)行完,T1才繼續(xù)執(zhí)行)
當(dāng)然實(shí)際多線程之間交互比上圖還要隨機(jī)。
dict讀取數(shù)據(jù)是線程安全,但是被反復(fù)讀寫(xiě)就容易出現(xiàn)數(shù)據(jù)混亂。
如果我們要設(shè)計(jì)一個(gè)線程安全的函數(shù),那么它必須不涉及任何共享變量或者是完全沒(méi)有狀態(tài)依賴(lài)的函數(shù)
def thread_safe_method(): pass
比如下面的加法函數(shù),不管多少個(gè)線程調(diào)用,返回值永遠(yuǎn)是預(yù)期的a+b。
def add(a, b): return a + b
許我們可以把多線程轉(zhuǎn)換為單線程,這個(gè)需要一個(gè)線程安全的媒介。
關(guān)于“Python線程安全實(shí)例分析”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí),可以關(guān)注億速云行業(yè)資訊頻道,小編每天都會(huì)為大家更新不同的知識(shí)點(diǎn)。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。