python多執行緒程式設計1— python對多執行緒的支援

pythontab發表於2013-04-02

多執行緒程式設計必須理解的一些基本概念,適用於所有程式語言。內容:

併發式程式設計

多工作業系統

多執行緒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建立和啟動執行緒。


相關文章