多執行緒程式設計必須理解的一些基本概念,適用於所有程式語言。內容:
並發式程式設計
多任務作業系統
多執行緒vs多行程
執行緒
執行緒的生命週期
執行程式
式的類型軟體對並發式程式設計並發送式的視角。並發式程式設計將軟體視為任務和資源的組合-任務之間競爭和共享資源,當資源滿足時執行任務,否則等待資源。 並發式程式設計使得軟體易於理解和重複使用,在某些場景能夠大幅提升效能。 多任務作業系統要實現並發,首先需要作業系統的支援。現在的作業系統大部分都是多任務作業系統,可以「同時」執行多個任務。 多任務可以在進程或執行緒的層級執行。 進程是指一個記憶體中運行的應用程序,每個進程都有自己獨立的一塊記憶體空間。多任務作業系統可以「並發」執行這些進程。 執行緒是指進程中亂序、多次執行的程式碼區塊,多個執行緒可以「同時」運行,所以認為多個執行緒是「並發」的。多執行緒的目的是為了最大限度的利用CPU資源。例如一個JVM進程中,所有的程式碼都以執行緒的方式運作。 這裡面的「同時」、「並發」只是一種宏觀的感受,實際上從微觀層面看只是進程/線程的輪換執行,只不過切換的時間非常短,所以產生了「並行」的感覺。 多執行緒vs多行程作業系統會為每個行程分配不同的記憶體區塊,而多個執行緒共享程序的記憶體區塊。這帶來最直接的不同就是創建執行緒的開銷遠小於建立程序的開銷。 同時,由於記憶體區塊不同,所以進程之間的通訊相對困難。需要採用pipe/named pipe,signal, message queue, shared memory,socket等手段;而線程間的通訊簡單快速,就是共享進程內的全域變數。 但是,進程的調度由作業系統負責,執行緒的調度就需要我們自己來考慮,避免死鎖,飢餓,活鎖,資源枯竭等情況的發生,這會增加一定的複雜度。而且,由於線程之間共享內存,我們還需要考慮線程安全性的問題。 線程安全以為線程間共享進程中的全局變量,所以當其他線程改變了共享的變量時,可能會對本線程產生影響。所謂線程安全的約束是指一個函數被多個並發線程反覆呼叫時,要一直產生正確的結果。要確保線程安全,主要是透過加鎖的方式保證共享變數的正確存取。 比線程安全更嚴格的約束是"可重入性",即函數在一個線程內執行的過程中被暫停,接下來又在另一個線程內被調用,之後在返回原始線程繼續執行。在整個過程中都能保證正確執行。保證可重入性,通常透過製作全域變數的本地拷貝來實現。 線程的生命週期所謂的xx生命週期,其實就是某物件的包含產生和銷毀的一張狀態圖。線的生命週期如下圖所示:各狀態的說明如下:New新建。新建立的執行緒經過初始化後,進入Runnable狀態。 Runnable就緒。等待線程調度。調度後進入運轉狀態。 Running運行。 Blocked阻塞。暫停運行,解除阻塞後進入Runnable狀態重新等待調度。 Dead消亡。執行緒方法執行完畢返回或異常終止。 可能有3種情況從Running進入Blocked:同步:執行緒中取得同步鎖,但是資源已經被其他執行緒鎖定時,進入Locked狀態,直到該資源可取得(取得的順序由Lock佇列控制)睡眠:執行緒運行sleep()或join()方法後,執行緒進入Sleeping狀態。差別在於sleep等待固定的時間,而join是等待子執行緒執行完畢。當然join也可以指定一個「超時時間」。從語意上來說,如果兩個執行緒a,b, 在a中呼叫b.join(),相當於合併(join)成一個執行緒。最常見的情況是在主執行緒中join所有的子執行緒。 等待:在執行緒執行wait()方法後,執行緒進入Waiting狀態,等待其他執行緒的通知(notify)。 執行緒的類型主執行緒:當一個程式啟動時,就有一個行程被作業系統(OS)創建,同時一個執行緒也立刻執行,該執行緒通常叫做程式的主執行緒(Main Thread)。每個行程至少有一個主線程,主線程通常最後關閉。 子線程:在程式中創建的其他線程,相對於主線程來說就是這個主線程的子線程。 守護線程:daemon thread,對線程的一種標識。守護線程為其他線程提供服務,例如JVM的垃圾回收線程。當剩下的全是守護線程時,進程就會退出。 前台線:相對於守護執行緒的其他執行緒稱為前台執行緒。
python對多執行緒的支援
Python虛擬機器使用GIL(Global Interpreter Lock,全域解釋器鎖定)來互斥執行緒對共享資源的訪問,暫時無法利用多處理器的優勢。
語言層面
在語言層面,Python對多執行緒提供了很好的支持,Python中多執行緒相關的模組包括:thread,threading,Queue。可以方便地支援建立執行緒、互斥鎖、信號量、同步等特性。
thread:多執行緒的底層支援模組,一般不建議使用。
threading:對thread進行了封裝,將一些執行緒的操作物件化,提供下列類別:
Thread 執行緒類別
Timer與Thread類似,但要等待一段時間後才開始執行
Lock 鎖原語
RLock 可重入鎖。讓單執行緒可以再次取得已經取得的鎖定
Condition 條件變量,能讓一個執行緒停下來,等待其他執行緒滿足某個「條件」
Event 通用的條件變數。多個執行緒可以等待某個事件發生,在事件發生後,所有的執行緒都被啟動
Semaphore為等待鎖的執行緒提供一個類似「等候室」的結構
BoundedSemaphore 與semaphore類似,但不允許超過初始值
Queue:實作了多生產者(Producer)、多消費者(Consumer)的佇列,支援鎖原語,能夠在多個執行緒之間提供很好的同步支援。提供的類別:
Queue佇列
LifoQueue後入先出(LIFO)佇列
PriorityQueue 優先佇列
其中Thread類別是你主要的執行緒類,可以建立行程實例。這類提供的函數包括:
getName(self) 傳回執行緒的名字
isAlive(self) 布林標誌,表示這個執行緒是否還在運作中
isDaemon(self) 傳回執行緒的daemon標誌
join(self
join(selfjoin(self
join(selfjoin(self
join(selfjoin(self , timeout=None) 程式掛起,直到執行緒結束,如果給出timeout,則最多阻塞timeout秒
run(self) 定義執行緒的功能函數
setDaemon(self, daemonic) 把執行緒的daemon標誌設為daemonicsetDaemon(self, daemonic) 把執行緒的daemon標誌設為daemonicsetDaemon(self, daemonic) 把執行緒的daemon標誌設為daemonic
setName(self, name) 設定執行緒的名字
start(self) 開始執行緒執行
第三方支援
如果你特別在意效能,還可以考慮一些「微線程」的實作:
Stackless Python: Python的一個增強版本,提供了對微線程的支援。微線程是輕量級的線程,在多個線程間切換所需的時間更多,佔用資源也更少。