Python低層多線程接口_thread模塊的用法和特性
一、進(jìn)程和線程的區(qū)別
并行(多任務(wù)處理)是現(xiàn)代操作系統(tǒng)的基本特性,一個(gè)程序可以同時(shí)處理很多任務(wù)而不被阻塞。多任務(wù)處理的基本方式有兩種:進(jìn)程分支和線程派生。
進(jìn)程分支就是從當(dāng)前進(jìn)程復(fù)制一個(gè)程序副本,程序中的內(nèi)存副本,文件描述符等都會(huì)被復(fù)制,子進(jìn)程改變一個(gè)全局對(duì)象只是修改本地的副本,不會(huì)影響到父進(jìn)程。常用在啟動(dòng)獨(dú)立的程序。
線程派生和進(jìn)程分支類(lèi)似,但是子線程依然在原進(jìn)程中運(yùn)行,所有的子線程都會(huì)共享進(jìn)程中的全局對(duì)象,相對(duì)于進(jìn)程分支,少了一個(gè)"復(fù)制"的操作,因此更加輕量化,且由于共享進(jìn)程對(duì)象,相當(dāng)于自帶了線程間的通信機(jī)制(進(jìn)程間的通信則需要借助管道,套接字,信號(hào)等外部工具)。常用在處理一些輕量級(jí)任務(wù)。
但這些共享對(duì)象的訪問(wèn)可能出現(xiàn)沖突,_thread也提供了鎖機(jī)制來(lái)同步化對(duì)象訪問(wèn),例如修改一個(gè)對(duì)象時(shí),先獲取鎖,完成后再釋放,這樣就可以保證任意時(shí)間點(diǎn),最多能有1個(gè)線程能修改這個(gè)共享對(duì)象,防止出現(xiàn)混亂。
二、_thread模塊的用法
_thread模塊中的start_new_thread可以開(kāi)啟一個(gè)新的線程并運(yùn)行指定程序。
2.1 派生線程
當(dāng)程序啟動(dòng)時(shí),其實(shí)它就已經(jīng)啟動(dòng)了一個(gè)線程,這個(gè)就是主線程。通過(guò)_thread.start_new_thread方法,傳入一個(gè)函數(shù)對(duì)象和一個(gè)參數(shù)元組,就可以開(kāi)啟新線程來(lái)執(zhí)行所傳入的函數(shù)對(duì)象。簡(jiǎn)單的演示如下:
import _thread as thread def func(id): print('我是 {} 號(hào)子線程'.format(id)) for i in range(5): thread.start_new_thread(func,(i,))
上面代碼執(zhí)行示意圖如下:for循環(huán)執(zhí)行了5次start_new_thread,這會(huì)開(kāi)啟5個(gè)線程,每個(gè)線程去執(zhí)行func函數(shù)。因?yàn)榫€程是并行的,所以輸出的順序并不是0,1,2,3,4而是混亂的。注意3,4號(hào)子線程打印出現(xiàn)了重疊,這是因?yàn)樗鼈児蚕硪粋€(gè)標(biāo)準(zhǔn)輸出流,而我們沒(méi)有做同步化訪問(wèn)控制,因而它們同時(shí)打印輸出。
2.2 同步化訪問(wèn)控制
由于子線程可以共享進(jìn)程中的資源,這既是一個(gè)優(yōu)勢(shì)(方便線程間通信),也帶來(lái)了共享資源訪問(wèn)的問(wèn)題,如果多個(gè)子線程同時(shí)修改一個(gè)共享資源,那么就容易出現(xiàn)沖突,例如上面的輸出重疊。
為了解決這類(lèi)問(wèn)題,_thread模塊中的allocate_lock方法提供了一個(gè)簡(jiǎn)單的鎖機(jī)制來(lái)控制共享訪問(wèn),每次獲取共享資源時(shí)先獲取鎖,可以保證任何時(shí)間點(diǎn)最多只有1個(gè)線程可以訪問(wèn)該資源。示例如下:
import _thread as thread lock = thread.allocate_lock() # 定義一個(gè)鎖 def func(id): with lock: # 用上下文管理器自動(dòng)控制鎖的獲取和釋放 print('我是 {} 號(hào)子線程'.format(id)) for i in range(5): thread.start_new_thread(func,(i,))
with lock會(huì)自動(dòng)管理鎖對(duì)象的獲取和釋放,默認(rèn)線程會(huì)一直等待直到鎖的獲取,如果想要更精細(xì)的控制可以使用下面的方式:
lock.acquire() # 獲取鎖 print('我是 {} 號(hào)子線程'.format(id)) lock.release() # 釋放鎖
lock.acquire()有2個(gè)可選的參數(shù):blocking=True代表無(wú)法獲取鎖時(shí)等待,blocking=False代表如無(wú)法立刻獲取鎖則返回。timeout=-1代表等待的秒數(shù)(-1代表無(wú)限等待),此參數(shù)只有在blocking設(shè)置為T(mén)rue時(shí)才能指定。
2.3 子線程的退出控制
_thread派生子線程后,如果主線程運(yùn)行完畢,而子線程依然在運(yùn)行中,那么所有子線程就會(huì)隨著主線程的退出而被終止。
我們?cè)谧舆M(jìn)程中增加一個(gè)sleep來(lái)模擬長(zhǎng)時(shí)間任務(wù),讓其運(yùn)行時(shí)長(zhǎng)超過(guò)主線程。將下面的代碼保存到一個(gè)thread1.py中,以一個(gè)獨(dú)立進(jìn)程啟動(dòng):
import _thread as thread, time lock = thread.allocate_lock() # 定義一個(gè)鎖 def func(id): time.sleep(id) # 子線程會(huì)睡眠,運(yùn)行時(shí)長(zhǎng)將超過(guò)主線程 with lock: print('我是 {} 號(hào)子線程'.format(id)) for i in range(5): thread.start_new_thread(func,(i,)) print('主線程結(jié)束,退出...') # 主線程退出時(shí)打印提示
可以看到,主線程打印了退出提示,但子線程卻沒(méi)有任何輸出,這是因?yàn)橹骶€程運(yùn)行的時(shí)間非常短,當(dāng)其退出時(shí),所有子線程都終止了。這種情況顯然不是我們想看到的。示例圖如下:
2.3.1 通過(guò)sleep等待子線程運(yùn)行結(jié)束
為了解決上面的問(wèn)題,一個(gè)簡(jiǎn)單的解決方案可以在主線程中加一個(gè)sleep,讓其等待一段時(shí)間再退出,但這個(gè)時(shí)間我們只能預(yù)估。在上面的代碼基礎(chǔ)上,增加一個(gè)time.sleep(3),讓主線程退出前等待3秒,保存為thread2.py,再次執(zhí)行:
import _thread as thread, time lock = thread.allocate_lock() # 定義一個(gè)鎖 def func(id): time.sleep(id) # 子線程會(huì)睡眠,運(yùn)行時(shí)長(zhǎng)將超過(guò)主線程 with lock: print('我是 {} 號(hào)子線程'.format(id)) for i in range(5): thread.start_new_thread(func,(i,)) time.sleep(3) # 主線程等待3秒再退出 print('主線程結(jié)束,退出...') # 主線程退出時(shí)打印提示
可以看到部分子進(jìn)程運(yùn)行完畢,但還有部分子進(jìn)程未完成,因此這種方法不是很準(zhǔn)確,雖然你可以給一個(gè)足夠長(zhǎng)的時(shí)間來(lái)保證所有子進(jìn)程運(yùn)行結(jié)束,但如果進(jìn)程長(zhǎng)時(shí)間不結(jié)束,也會(huì)占用系統(tǒng)資源。
2.3.2 通過(guò)鎖的狀態(tài)監(jiān)測(cè)子進(jìn)程結(jié)束
_thread.allocate_lock除了可以控制共享對(duì)象的訪問(wèn),還可以用來(lái)傳遞全局狀態(tài),下面定義了包含5把鎖的列表,每個(gè)子線程執(zhí)行完成后會(huì)去獲取其中對(duì)應(yīng)位置上的鎖,在主線程中通過(guò)lock.locked()來(lái)檢查是否所有的鎖都被獲取,當(dāng)所有鎖都被獲取時(shí)(代表所有子線程都結(jié)束),主線程退出。將下面代碼保存到thread3.py中,再次運(yùn)行:
import _thread as thread, time lock = thread.allocate_lock() # 定義一個(gè)鎖 exit_locks = [thread.allocate_lock() for I in range(5)] # 定義一個(gè)列表,包含5把鎖,對(duì)應(yīng)稍后啟動(dòng)的5個(gè)子線程 def func(id): time.sleep(id) # 子線程會(huì)睡眠,運(yùn)行時(shí)長(zhǎng)將超過(guò)主線程 with lock: print('我是 {} 號(hào)子線程'.format(id)) exit_locks[id].acquire() # 執(zhí)行完成后獲取exit_locks中對(duì)應(yīng)位置的鎖 for i in range(5): thread.start_new_thread(func,(i,)) for lock in exit_locks: while not lock.locked(): pass # lock.locked()檢測(cè)鎖是否已被獲取 print('主線程結(jié)束,退出...') # 主線程退出時(shí)打印提示
測(cè)試時(shí)可以發(fā)現(xiàn),主線程會(huì)在所有子線程執(zhí)行完畢后立刻退出,即不會(huì)提前導(dǎo)致子線程終止,也不會(huì)推遲浪費(fèi)系統(tǒng)資源。
2.3.3 通過(guò)共享變量監(jiān)測(cè)子進(jìn)程結(jié)束
由于子線程可以共享進(jìn)程中的變量,因此子線程中對(duì)共享對(duì)象的修改在主線程也可以看到,我們可以將上面的鎖替換為簡(jiǎn)單的變量,可以達(dá)到相同的效果,下面使用一個(gè)共享列表,通過(guò)在子線程中修改變量值傳遞狀態(tài),將下面代碼保存為thread4.py并執(zhí)行:
import _thread as thread, time lock = thread.allocate_lock() # 定義一個(gè)鎖 exit_flags = [False]*5 # 定義一個(gè)全局共享列表,包含5個(gè)布爾變量False def func(id): time.sleep(id) # 子線程會(huì)睡眠,運(yùn)行時(shí)長(zhǎng)將超過(guò)主線程 with lock: print('我是 {} 號(hào)子線程'.format(id)) exit_flags[id] = True # 執(zhí)行完成后將共享列表中對(duì)應(yīng)位置的值改為T(mén)rue for i in range(5): thread.start_new_thread(func,(i,)) while False in exit_flags:pass # 檢測(cè)列表中是否有False,如果全部為T(mén)ure,代表所有子線程執(zhí)行完畢 print('主線程結(jié)束,退出...') # 主線程退出時(shí)打印提示
可以看到主線程會(huì)等待子線程執(zhí)行完畢后退出,這種方式相比上面可以節(jié)約鎖分配的資源,看上去也更加簡(jiǎn)單。
以上即是_thread模塊的基本用法?;赺thread模塊還有高級(jí)的threading模塊,_threading模塊是基于類(lèi)和對(duì)象的高級(jí)接口,并提供了額外的控制工具,例如threading.join()可以實(shí)現(xiàn)等待子進(jìn)程退出。
到此這篇關(guān)于Python低層多線程接口_thread模塊的用法和特性的文章就介紹到這了,更多相關(guān)Python低層多線程接口內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Python selenium抓取虎牙短視頻代碼實(shí)例
這篇文章主要介紹了Python selenium抓取虎牙短視頻代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03Python?搭建?FastAPI?項(xiàng)目的詳細(xì)過(guò)程
這篇文章主要介紹了Python搭建FastAPI項(xiàng)目的過(guò)程,本文通過(guò)圖文實(shí)例相結(jié)合給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-09-09Python shelve模塊實(shí)現(xiàn)解析
這篇文章主要介紹了Python shelve模塊實(shí)現(xiàn)解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-08-08Python開(kāi)發(fā)文字版密室逃脫游戲的實(shí)例(含代碼)
密室逃脫游戲是一種頗受歡迎的解謎類(lèi)游戲,玩家通常需要通過(guò)觀察、推理、合作等方式解決一系列難題,以逃脫困境,在這篇博文中,我們將使用Python開(kāi)發(fā)一個(gè)文字版密室逃脫游戲,旨在通過(guò)簡(jiǎn)單的文本交互來(lái)呈現(xiàn)游戲的趣味性與挑戰(zhàn)性2025-04-04Python ckeditor富文本編輯器代碼實(shí)例解析
這篇文章主要介紹了Python ckeditor富文本編輯器代碼實(shí)例解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06Python貪心算法Greedy Algorithm解決案例小結(jié)
這篇文章主要為大家介紹了Python貪心算法Greedy Algorithm解決案例小結(jié),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06Python實(shí)現(xiàn)TOPSIS分析法的示例代碼
TOPSIS法是一種常用的綜合評(píng)價(jià)方法,其能充分利用原始數(shù)據(jù)的信息,其結(jié)果能精確反應(yīng)各評(píng)價(jià)方案之間的差距。本文將利用Python實(shí)現(xiàn)這一方法,感興趣的可以了解一下2023-02-02關(guān)于python3安裝pip及requests庫(kù)的導(dǎo)入問(wèn)題
小編最近快畢業(yè)了,閑著無(wú)事學(xué)習(xí)下python的內(nèi)容在學(xué)習(xí)到requsets庫(kù)的導(dǎo)入問(wèn)題時(shí)遇到一些問(wèn)題,通過(guò)查找相關(guān)資料問(wèn)題順利解決,今天小編把問(wèn)題解決思路及注意事項(xiàng)分享給大家供大家參考學(xué)習(xí)2021-05-05基于Tensorflow使用CPU而不用GPU問(wèn)題的解決
今天小編就為大家分享一篇基于Tensorflow使用CPU而不用GPU問(wèn)題的解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-02-02