多執行緒程式設計必須理解的一些基本概念,適用於所有程式語言。內容:
併發式程式設計
多工作業系統
多執行緒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, timeout=None) 程式掛起,直到執行緒結束,如果給出timeout,則最多阻塞timeout秒
run(self) 定義執行緒的功能函式
setDaemon(self, daemonic) 把執行緒的daemon標誌設為daemonic
setName(self, name) 設定執行緒的名字
start(self) 開始執行緒執行
第三方支援
如果你特別在意效能,還可以考慮一些“微執行緒”的實現:
Stackless Python:Python的一個增強版本,提供了對微執行緒的支援。微執行緒是輕量級的執行緒,在多個執行緒間切換所需的時間更多,佔用資源也更少。
greenlet:是 Stackless 的副產品,其將微執行緒稱為 “tasklet” 。tasklet執行在偽併發中,使用channel進行同步資料交換。而”greenlet”是更加原始的微執行緒的概念,沒有排程。你可以自己構造微執行緒的排程器,也可以使用greenlet實現高階的控制流。
下一節,將開始用python建立和啟動執行緒。