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