【python进阶】_多线程多进程
進(jìn)程就是運(yùn)行著的程序
線程就是操作系統(tǒng)創(chuàng)建的,每個(gè)線程對(duì)應(yīng)一個(gè)代碼執(zhí)行的數(shù)據(jù)結(jié)構(gòu),保存了代碼執(zhí)行過程中的重要的狀態(tài)信息。系統(tǒng)中每個(gè)進(jìn)程里面至少包含一個(gè)線程。沒有線程,操作系統(tǒng)沒法管理和維護(hù)代碼運(yùn)行的狀態(tài)信息,所以沒有創(chuàng)建線程之前,操縱系統(tǒng)是不會(huì)執(zhí)行我們的代碼的。
現(xiàn)在計(jì)算機(jī)上面,CPU是多核的,每個(gè)核都可以執(zhí)行代碼。要讓多個(gè)CPU核心同時(shí)去執(zhí)行任務(wù),我們的程序必須創(chuàng)建多個(gè)進(jìn)程,讓CPU執(zhí)行 多個(gè)線程 對(duì)應(yīng)的代碼。
python代碼中創(chuàng)建新線程
python3將系統(tǒng)調(diào)用創(chuàng)建線程的功能封裝在標(biāo)準(zhǔn)threading中。
print('主線程執(zhí)行代碼') # 從 threading 庫中導(dǎo)入Thread類 from threading import Thread from time import sleep# 定義一個(gè)函數(shù),作為新線程執(zhí)行的入口函數(shù) def threadFunc(arg1,arg2):print('子線程 開始')print(f'線程函數(shù)參數(shù)是:{arg1}, {arg2}')sleep(5)print('子線程 結(jié)束')# 創(chuàng)建 Thread 類的實(shí)例對(duì)象 thread = Thread(# target 參數(shù) 指定 新線程要執(zhí)行的函數(shù)# 注意,這里指定的函數(shù)對(duì)象只能寫一個(gè)名字,不能后面加括號(hào),# 如果加括號(hào)就是直接在當(dāng)前線程調(diào)用執(zhí)行,而不是在新線程中執(zhí)行了target=threadFunc, # 如果 新線程函數(shù)需要參數(shù),在 args里面填入?yún)?shù)# 注意參數(shù)是元組, 如果只有一個(gè)參數(shù),后面要有逗號(hào),像這樣 args=('參數(shù)1',)args=('參數(shù)1', '參數(shù)2') )# 執(zhí)行start 方法,就會(huì)創(chuàng)建新線程, # 并且新線程會(huì)去執(zhí)行入口函數(shù)里面的代碼。 # 這時(shí)候 這個(gè)進(jìn)程 有兩個(gè)線程了。 thread.start()# 主線程的代碼執(zhí)行 子線程對(duì)象的join方法, # 就會(huì)等待子線程結(jié)束,才繼續(xù)執(zhí)行下面的代碼 thread.join() print('主線程結(jié)束')創(chuàng)建了一個(gè)Thread實(shí)例對(duì)象,其中,Thread類的初始化參數(shù) 有兩個(gè)
target參數(shù) 是指定新線程的?入口函數(shù), 新線程創(chuàng)建后就會(huì) 執(zhí)行該入口函數(shù)里面的代碼,
args 指定了 傳給 入口函數(shù)threadFunc 的參數(shù)。 線程入口函數(shù) 參數(shù),必須放在一個(gè)元組里面,里面的元素依次作為入口函數(shù)的參數(shù)。
注意,上面的代碼只是創(chuàng)建了一個(gè)Thread實(shí)例對(duì)象, 但這時(shí),新的線程還沒有創(chuàng)建。要?jiǎng)?chuàng)建線程,必須要調(diào)用 Thread 實(shí)例對(duì)象的?start方法 。新的線程才創(chuàng)建成功,并開始執(zhí)行 入口函數(shù)threadFunc 里面的代碼
有的時(shí)候, 一個(gè)線程需要等待其它的線程結(jié)束,比如需要根據(jù)其他線程運(yùn)行結(jié)束后的結(jié)果進(jìn)行處理。這時(shí)可以使用 Thread對(duì)象的?join?方法:如果一個(gè)線程A的代碼調(diào)用了 對(duì)應(yīng)線程B的Thread對(duì)象的?join?方法,線程A就會(huì)停止繼續(xù)執(zhí)行代碼,等待線程B結(jié)束。 線程B結(jié)束后,線程A才繼續(xù)執(zhí)行后續(xù)的代碼。所以主線程在執(zhí)行上面的代碼時(shí),就暫停在此處, 一直要等到 新線程執(zhí)行完畢,退出后,才會(huì)繼續(xù)執(zhí)行后續(xù)的代碼。
共享數(shù)據(jù)的訪問控制
from threading import Thread from time import sleepbank = {'byhy' : 0 }# 定義一個(gè)函數(shù),作為新線程執(zhí)行的入口函數(shù) def deposit(theadidx,amount):balance = bank['byhy']# 執(zhí)行一些任務(wù),耗費(fèi)了0.1秒sleep(0.1)bank['byhy'] = balance + amountprint(f'子線程 {theadidx} 結(jié)束')theadlist = [] for idx in range(10):thread = Thread(target = deposit,args = (idx,1))thread.start()# 把線程對(duì)象都存儲(chǔ)到 threadlist中theadlist.append(thread)for thread in theadlist:thread.join()print('主線程結(jié)束') print(f'最后我們的賬號(hào)余額為 {bank["byhy"]}')在上面代碼中,我們預(yù)期的結(jié)果是余額為10,但是輸出的結(jié)果為1;這是因?yàn)槎鄠€(gè)線程同時(shí)調(diào)用deposit時(shí),可能出現(xiàn)一個(gè)線程覆蓋另一個(gè)線程的結(jié)果的問題。解決這個(gè)問題可以使用threading庫里面的鎖對(duì)象Lock去保護(hù),如下:
from time import sleepbank = {'byhy' : 0 }# 定義一個(gè)函數(shù),作為新線程執(zhí)行的入口函數(shù) def deposit(theadidx,amount):balance = bank['byhy']# 執(zhí)行一些任務(wù),耗費(fèi)了0.1秒sleep(0.1)bank['byhy'] = balance + amountfor idx in range(10):deposit (idx,1)print(f'最后我們的賬號(hào)余額為 {bank["byhy"]}') 代碼都是 串行 執(zhí)行的。 不存在多線程同時(shí)訪問 bank對(duì)象 的問題,運(yùn)行結(jié)果一切都是正常的?,F(xiàn)在我們程序代碼中,有多個(gè)線程,并且在這個(gè)幾個(gè)線程中都會(huì)去調(diào)用 deposit,就有可能同時(shí)操作這個(gè)bank對(duì)象,就有可能出一個(gè)線程覆蓋另外一個(gè)線程的結(jié)果的問題。這時(shí),可以使用 threading庫里面的鎖對(duì)象 Lock 去保護(hù)。我們修改多線程代碼,如下:from threading import Thread,Lock from time import sleepbank = {'byhy' : 0 }bankLock = Lock()# 定義一個(gè)函數(shù),作為新線程執(zhí)行的入口函數(shù) def deposit(theadidx,amount):# 操作共享數(shù)據(jù)前,申請(qǐng)獲取鎖bankLock.acquire()balance = bank['byhy']# 執(zhí)行一些任務(wù),耗費(fèi)了0.1秒sleep(0.1)bank['byhy'] = balance + amountprint(f'子線程 {theadidx} 結(jié)束')# 操作完共享數(shù)據(jù)后,申請(qǐng)釋放鎖bankLock.release()theadlist = [] for idx in range(10):thread = Thread(target = deposit,args = (idx,1))thread.start()# 把線程對(duì)象都存儲(chǔ)到 threadlist中theadlist.append(thread)for thread in theadlist:thread.join()print('主線程結(jié)束') print(f'最后我們的賬號(hào)余額為 {bank["byhy"]}')輸出結(jié)果如下:
Lock 對(duì)象的acquire方法 是申請(qǐng)鎖。每個(gè)線程在 操作共享數(shù)據(jù)對(duì)象之前,都應(yīng)該 申請(qǐng)獲取操作權(quán),也就是 調(diào)用該 共享數(shù)據(jù)對(duì)象對(duì)應(yīng)的鎖對(duì)象的acquire方法。如果線程A 執(zhí)行如下代碼,調(diào)用acquire方法的時(shí)候,別的線程B 已經(jīng)申請(qǐng)到了這個(gè)鎖, 并且還沒有釋放,那么 線程A的代碼就在此處 等待 線程B 釋放鎖,不去執(zhí)行后面的代碼。直到線程B 執(zhí)行了鎖的 release 方法釋放了這個(gè)鎖, 線程A 才可以獲取這個(gè)鎖,就可以執(zhí)行下面的代碼了。如果這時(shí)線程B 又執(zhí)行 這個(gè)鎖的acquire方法, 就需要等待線程A 執(zhí)行該鎖對(duì)象的release方法釋放鎖, 否則也會(huì)等待,不去執(zhí)行后面的代碼。
Python程序中當(dāng)所有的 `非daemon線程` 結(jié)束了,整個(gè)程序才會(huì)結(jié)束
總結(jié)
以上是生活随笔為你收集整理的【python进阶】_多线程多进程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【图像处理opencv】_图像边缘
- 下一篇: 【python进阶】_装饰器