簡述
多線程設計在系統中是比較關鍵的部分,對於系統效能的提高以及一個較為複雜架構的構建都是很重要的。 進程與線程差別
這部分許多資料可供參考,比如一些動畫介紹,還有CSAPP書中也詳細講到。在嵌入式系統中一般是沒有進程和線程區分概念的,因為嵌入式系統就跑一個程式(一個main入口),通過RTOS管理其中的各個線程(一般稱為task),其實總的就一個進程,可以獨享嵌入式系統的Flash, RAM等資源,也不會有IPC等技術和概念。
在多使用者作業系統,一般非RTOS就是吧,Linux, Windows, Android, MacOS, IOS,上面是會有許許多多的程式可以同時被運行,最後被終止的。一個程式其實就是一段二進位代碼,放在這些電腦的儲存空間上,只有這個程式被載入並執行,此時就有針對這個程式的生命週期和進程概念了,因此其實進程指的是程式的一次執行,進程有其獨立的記憶體、棧、地址空間等,處理序間通訊需要用IPC進行處理序間通訊。 Python中的線程
Python設計之初的考慮是在Python解譯器主迴圈中同時只有一個線程在執行,即Python解譯器可以運行多個線程,但是任意時刻只有一個線程在解譯器中運行。而做到這一點是通過Python的Global Interpreter Lock-GIL來實現的:
Python解譯器使用GIL控制線程執行首先設定GIL,並切換到一個線程去執行然後運行指定數量的位元組碼指令,或者線程主動讓出控制把線程設定為睡眠狀態,並解鎖GIL
Python多線程編程-threading模組
目前需要在Python代碼中使用多線程設計,都是import threading moduel。目前Python的線程沒有優先順序區分,也沒有線程組,線程不可銷毀、停止、掛起、恢複或中斷。 threading module的方法
| 方法名 |
詳細說明 |
| threading.active_count() |
返回當前處於啟用狀態的Thread對象個數,返回的個數等於threading.enumerate()返回的list的長度 |
| threading.current_thread() |
返回當前的Thread對象,相當於調用者的線程 |
| threading.get_ident() |
返回當前線程的線程identifier,這是一個非零值 |
| threading.enumerate() |
返回當前處於啟用狀態的Thread對象,以list形式返回,包含daemonic線程,dummy線程和main線程 |
| threading.main_thread() |
返回main線程對象,一般是Python解譯器開始啟動並執行線程 |
| threading.settrace(func) |
為所有從threading module建立的線程設定一個trace函數,該函數會在每個線程的run()方法被調用前,被傳給sys.settrace() |
| threading.setprofile(func) |
為所有從threading module建立的線程設定一個profile函數,該函數會在每個線程的run()方法被調用前,被傳給sys.setprofile() |
| threading.stack_size([size]) |
當建立一個新的線程時返回該線程使用的stack size,size參數可選,用來指定所建立線程的stack size,必須是0或是一個至少為32768的正整數。size未指定會預設使用0值,RuntimeError表示改變stack size不支援,ValueError表示stack size非法值。32K的stack size是目前支援最小的值,足夠的stack空間主要是解譯器本身需要佔用。一般memory是4096 Bytes一個page,Stack size建議設定為4K Bytes的倍數 |
threading module的常數
| 常數 |
詳細說明 |
| threading.TIMEOUT_MAX |
阻塞函數(Lock.acquire(), RLock.acquire(), Condition.wait()等)的允許最大的逾時值,如果使用的timeout參數大於這個最大值,就會發生OverflowError |
threading module的Thread類(對象)
Thread類是可以單獨控制和啟動並執行線程,可以通過傳遞一個可調用對象給構造器,或者重寫子類的run()方法來指定線程的運作。線程對象被建立,就必須通過start()方法來開始運作,此時會調用其run()方法,線程就啟用了。直到run()方法終止,線程就停止。
其他線程可以調用本線程的join()方法,此時調用本線程join()方法的線程會阻塞,等待本線程執行結束退出,再繼續調用線程的後續執行。
線程也可以標識為daemon線程,在Python程式退出時,daemon線程仍然保留,知道關機時才會退出。
還有一種main線程,即開始運行Python程式的線程,為非daemon線程。
還有dummy線程,這種線程是在threading module之外開啟,比如通過調用的C代碼建立的線程。
| 類及方法 |
詳細說明 |
| Thread類 |
class threading.Thread(group=None,target=None,name=None,args=(),kwargs={},*,daemon=None),類構造器,group表示線程組,目前不支援該功能,用於後續擴充。target是可以被run()方法調用的對象。name是線程名稱。args是參數tuple,用於被target調用。kwargs是關鍵字參數dictionary,用於被target調用。daemon表明線程是否為daemon類別。在Thread子類重寫構造器時,必須首先調用Thread.init() |
| start() |
開始線程的生命週期,該方法會安排run()方法的調用,如果調用start()多次,會發生RuntimeError |
| run() |
線程的生命週期-activity |
| join(timeout=None) |
等待直到線程終止。如果timeout非零,其值為浮點數,要判斷join()是否逾時,需要使用is_alive()方法看,如果在調用了join()方法後再調用同一線程的is_alive()方法,如果還是alive的,說明join()逾時了。如果timeout為None,則調用者會一直阻塞直到被調用join()方法的線程終止。一個線程的join()可被多次調用 |
| name |
|
| getName() |
|
| setName() |
|
| ident |
線程idnetifier |
| is_alive() |
判斷線程是否alive,主要是看run()是否結束 |
| daemon |
daemon線程標誌 |
| isDaemon |
判斷是否daemon線程 |
| setDaemon |
設定為daemon線程 |
threading module的Lock類(對象)
Lock即primitive lock,原始鎖,只會處於兩個狀態中的一個,“locked”(初始態)或“unlocked”。並有acquire()和release()兩個方法。acquire()有阻塞和非阻塞兩種方式,阻塞是會一直等待直到鎖釋放,而非阻塞是如果擷取不到鎖就立即返回false。相關圖示如下:
threading module的RLock類(對象)
Reentrant Lock,可重新進入鎖,即對於同一線程而言,是可重新進入鎖,而對於其他線程而言,和上面的Lock沒有區別。
和Lock的區別在於:在某一線程acquire()一個lock時,發現lock已經被本線程之前acquire()過但還未release(),此時可以繼續acquire(),只是要進行同樣次數的release()後才會將lock最終unlocked。
所以可重新進入鎖就是基於Lock增加了lock的owner機制,lock的owner是自身,可以繼續acquire(),並將計數器加一。 threading module的Condition類(對象)
Condition Variable-條件變數,和Lock總是有著關聯,底層機制應當還是使用Lock來實現的,Condition Variable的一個很好的應用情境就是“生產者-消費者”模型:
# Consume an itemwith cv: cv.wait_for(an_item_is_available) get_an_available_item()# Produce one itemwith cv: make_an_item_available() cv.notify()
| 類與方法 |
詳細說明 |
| threading.Condition |
class threading.Condition(lock=None),用於實現條件變數對象,允許多個線程wait一個條件變數,直到被一個線程notify。如果lock參數非None,必須是從外部傳入的Lock或RLock。如果lock參數是None,會建立一個RLock |
| acquire(*args) |
acquire上面提到的傳入的或建立的lock |
| release() |
release上面提到的傳入的或建立的lock |
| wait(timeout=None) |
等待notify或到timeout發生,其實就是相當於acquire()一個lock,然後等待有人將其release() |
| wait_for(predicate, timeout=None) |
等待直到condition為True,predicate是可調用的且其結果是boolean值 |
| notify(n=1) |
預設喚醒一個等待condition的線程。這個方法可以喚醒最多n個等待condition的線程 |
| notify_all() |
喚醒所有等待condition的線程 |
condition機制如下圖所示:
即Consumer等待條件滿足,而Producer則觸發條件滿足,這樣來做線程間的同步和通訊。 threading module的Semaphore類(對象)
Semaphore內部維護一個計數器,每次acquire()計數器減一,每次release()計數器加一,計數器不能為負數。 threading module的Event類(對象)
線程通訊最為簡單的機制,一個線程拋出一個訊號,另外線程等待這個訊號。
| 類與方法 |
詳細說明 |
| Event |
class threading.Event。管理一個內部flag(True or False) |
| is_set() |
判斷內部flag是否True |
| set() |
將內部flag設定為True |
| clear() |
將內部flag設定為False |
| wait(timeout=None) |
阻塞直到內部flag為True,或者timeout時間到 |
threading module的Timer類(對象)
Timer用於某個動作需要等待一定時間後才開始執行。Timer是Thread的子類,除了start()方法,Timer還有cancel()方法來停止timer。
程式碼範例:
def hello(): print("hello, world")t = Timer(30.0, hello)t.start() # after 30 seconds, "hello, world" will be printed
| 類與方法 |
詳細說明 |
| Timer |
class threading.Timer(interval, function, args=None, kwargs=None)。timer將在interval的時間後運行function函數,args是function函數的參數,kwargs是function函數的關鍵字參數 |
| cancel() |
停止timer,取消timer後續函數的執行(僅在等待狀態有用) |
threading module的Barrier類(對象)
Barrier類用於多個線程間互相等待的情境,每個線程通過調用wait()嘗試傳輸barrier,並會block直到所有線程都傳輸了barrier。線程最終會同步release。
舉例:
b = Barrier(2, timeout=5)def server(): start_server() b.wait() while True: connection = accept_connection() process_server_connection(connection)def client(): b.wait() while True: connection = make_connection() process_client_connection(connection)
| 類和方法 |
詳細說明 |
| Barrier |
class threading.Barrier(parties, action=None, timeout=None)。parties是線程數目,action是線上程release時可調用的,timeout是沒有任何線程調用wait()的逾時時間 |
| wait(timeout=None) |
Pass the barrier,傳回值是0到parties-1的值 |
| reset() |
將Barrier恢複為預設狀態-空 |
| abort() |
將Barrier設定為broken狀態 |
| parties |
需要pass barrier的線程個數 |
| n_waiting |
當前等待barrier的線程個數 |
| broken |
布爾值,表明barrier是否broken |
Python多線程編程-queue模組
queue是同步的隊列類,multi-producer, multi-consumer隊列,主要用於threading編程中,多線程之間的安全通訊。queue實現了所有需要的鎖語義。且實現了3類queue:
| queue類別 |
詳細說明 |
| FIFO |
先進先出,一般queue |
| LIFO |
後進先出,像stack |
| Priority queue |
入口進行了分類,最低的入口最先出 |
queue module的類和異常
| 類或異常 |
詳細說明 |
| Queue |
class queue.Queue(maxsize=0),FIFO queue構造,maxsize是queue最大空間,如果queue滿,入隊就會block直到有出隊發生 |
| LifoQueue |
class queue.LifoQueue(maxsize=0),LIFO queue構造,maxsize是queue最大空間,如果queue滿,入隊就會block直到有出隊發生 |
| PriorityQueue |
class queue.PriorityQueue(maxsize=0),Priority queue構造,maxsize是queue最大空間,如果queue滿,入隊就會block直到有出隊發生 |
| Empty |
exception queue.Empty,隊空 |
| Full |
exception queue.Full,隊滿 |
queue module的Queue類(對象)
| 方法 |
詳細描述 |
| Queue.qsize() |
返回queue中大約的元素個數 |
| Queue.empty() |
判斷queue是否空 |
| Queue.full() |
判斷queue是否滿 |
| Queue.put(item, block=True, timeout=None) |
入隊 |
| Queue.put_nowait(item) |
和put(item,False)一樣 |
| Queue.get(block=True, timeout=None) |
出隊 |
| Queue.get_nowait() |
和get(False)一樣 |
另外還有兩個方法,用於支援入隊的任務是否被daemon消費者線程處理的跟蹤:
Queue.task_done()和Queue.join()。