Python:使用threading模塊實(shí)現(xiàn)多線(xiàn)程編程一[綜述]Python這門(mén)解釋性語(yǔ)言也有專(zhuān)門(mén)的線(xiàn)程模型,,Python虛擬機(jī)使用GIL(Global Interpreter Lock,,全局解釋器鎖)來(lái)互斥線(xiàn)程對(duì)共享資源的訪(fǎng)問(wèn),,但暫時(shí)無(wú)法利用多處理器的優(yōu)勢(shì)。 在Python中我們主要是通過(guò)thread和 threading這兩個(gè)模塊來(lái)實(shí)現(xiàn)的,其中Python的threading模塊是對(duì)thread做了一些包裝的,可以更加方便的被使用,所以我們使用 threading模塊實(shí)現(xiàn)多線(xiàn)程編程,。這篇文章我們主要來(lái)看看Python對(duì)多線(xiàn)程編程的支持。 在語(yǔ)言層面,,Python對(duì)多線(xiàn)程提供了很好的支持,,可以方便地支持創(chuàng)建線(xiàn)程、互斥鎖,、信號(hào)量,、同步等特性。下面就是官網(wǎng)上介紹threading模塊的基本資料及功能: 實(shí)現(xiàn)模塊
thread:多線(xiàn)程的底層支持模塊,,一般不建議使用,; threading:對(duì)thread進(jìn)行了封裝,,將一些線(xiàn)程的操作對(duì)象化。 threading模塊
Thread 線(xiàn)程類(lèi),,這是我們用的最多的一個(gè)類(lèi),,你可以指定線(xiàn)程函數(shù)執(zhí)行或者繼承自它都可以實(shí)現(xiàn)子線(xiàn)程功能; Timer與Thread類(lèi)似,,但要等待一段時(shí)間后才開(kāi)始運(yùn)行,; Lock 鎖原語(yǔ),這個(gè)我們可以對(duì)全局變量互斥時(shí)使用,; RLock 可重入鎖,,使單線(xiàn)程可以再次獲得已經(jīng)獲得的鎖; Condition 條件變量,,能讓一個(gè)線(xiàn)程停下來(lái),,等待其他線(xiàn)程滿(mǎn)足某個(gè)“條件”; Event 通用的條件變量,。多個(gè)線(xiàn)程可以等待某個(gè)事件發(fā)生,,在事件發(fā)生后,所有的線(xiàn)程都被激活,; Semaphore為等待鎖的線(xiàn)程提供一個(gè)類(lèi)似“等候室”的結(jié)構(gòu); BoundedSemaphore 與semaphore類(lèi)似,,但不允許超過(guò)初始值,; Queue:實(shí)現(xiàn)了多生產(chǎn)者(Producer)、多消費(fèi)者(Consumer)的隊(duì)列,,支持鎖原語(yǔ),,能夠在多個(gè)線(xiàn)程之間提供很好的同步支持。 其中Thread類(lèi)
是你主要的線(xiàn)程類(lèi),,可以創(chuàng)建進(jìn)程實(shí)例,。該類(lèi)提供的函數(shù)包括: getName(self) 返回線(xiàn)程的名字 isAlive(self) 布爾標(biāo)志,表示這個(gè)線(xiàn)程是否還在運(yùn)行中 isDaemon(self) 返回線(xiàn)程的daemon標(biāo)志 join(self, timeout=None) 程序掛起,,直到線(xiàn)程結(jié)束,,如果給出timeout,則最多阻塞timeout秒 run(self) 定義線(xiàn)程的功能函數(shù) setDaemon(self, daemonic) 把線(xiàn)程的daemon標(biāo)志設(shè)為daemonic setName(self, name) 設(shè)置線(xiàn)程的名字 start(self) 開(kāi)始線(xiàn)程執(zhí)行 其中Queue提供的類(lèi)
Queue隊(duì)列 LifoQueue后入先出(LIFO)隊(duì)列 PriorityQueue 優(yōu)先隊(duì)列 接下來(lái):
接下來(lái)的一系列文章,,我們將會(huì)用一個(gè)一個(gè)示例來(lái)展示threading的各個(gè)功能,,包括但不限于:兩種方式起線(xiàn)程、threading.Thread類(lèi)的重要函數(shù),、使用Lock互斥及RLock實(shí)現(xiàn)重入鎖,、使用Condition實(shí)現(xiàn)生產(chǎn)者和消費(fèi)者模型、使用Event和Semaphore多線(xiàn)程通信,。 Python:使用threading模塊實(shí)現(xiàn)多線(xiàn)程編程二[兩種方式起線(xiàn)程]在Python中我們主要是通過(guò)thread和threading這兩個(gè)模塊來(lái)實(shí)現(xiàn)的,,其中Python的threading模塊是對(duì)thread做了一些包裝的,,可以更加方便的被使用,所以我們使用threading模塊實(shí)現(xiàn)多線(xiàn)程編程,。一般來(lái)說(shuō),,使用線(xiàn)程有兩種模式,一種是創(chuàng)建線(xiàn)程要執(zhí)行的函數(shù),,把這個(gè)函數(shù)傳遞進(jìn)Thread對(duì)象里,,讓它來(lái)執(zhí)行;另一種是直接從Thread繼承,,創(chuàng)建一個(gè)新的class,,把線(xiàn)程執(zhí)行的代碼放到這個(gè)新的 class里。 將函數(shù)傳遞進(jìn)Thread對(duì)象
Python代碼
程序啟動(dòng)了3個(gè)線(xiàn)程,,并且打印了每一個(gè)線(xiàn)程的線(xiàn)程名字,,這個(gè)比較簡(jiǎn)單吧,處理重復(fù)任務(wù)就派出用場(chǎng)了,,下面介紹使用繼承threading的方式,; 繼承自threading.Thread類(lèi)
Python代碼
接下來(lái)的文章,將會(huì)介紹如何控制這些線(xiàn)程,,包括子線(xiàn)程的退出,,子線(xiàn)程是否存活及將子線(xiàn)程設(shè)置為守護(hù)線(xiàn)程(Daemon)。 Python:使用threading模塊實(shí)現(xiàn)多線(xiàn)程編程三[threading.Thread類(lèi)的重要函數(shù)]這篇文章主要介紹threading模塊中的主類(lèi)Thread的一些主要方法,,實(shí)例代碼如下:Python代碼
1 name相關(guān) 你可以為每一個(gè)thread指定name,,默認(rèn)的是Thread-No形式的,如上述實(shí)例代碼打印出的一樣:
當(dāng)然你可以指定每一個(gè)thread的name,,這個(gè)通過(guò)setName方法,,代碼: Python代碼
2 join方法 join方法原型如下,這個(gè)方法是用來(lái)阻塞當(dāng)前上下文,,直至該線(xiàn)程運(yùn)行結(jié)束: Python代碼
timeout可以設(shè)置超時(shí)蠶食 3 setDaemon方法 當(dāng)我們?cè)诔绦蜻\(yùn)行中,,執(zhí)行一個(gè)主線(xiàn)程,如果主線(xiàn)程又創(chuàng)建一個(gè)子線(xiàn)程,,主線(xiàn)程和子線(xiàn)程就分兵兩路,,當(dāng)主線(xiàn)程完成想退出時(shí),會(huì)檢驗(yàn)子線(xiàn)程是否完成,。如果子線(xiàn)程未完成,,則主線(xiàn)程會(huì)等待子線(xiàn)程完成后再退出。但是有時(shí)候我們需要的是,,只要主線(xiàn)程完成了,,不管子線(xiàn)程是否完成,都要和主線(xiàn)程一起退出,這時(shí)就可以用setDaemon方法,,并設(shè)置其參數(shù)為T(mén)rue,。 Python:使用threading模塊實(shí)現(xiàn)多線(xiàn)程編程四[使用Lock互斥鎖]前面已經(jīng)演示了Python:使用threading模塊實(shí)現(xiàn)多線(xiàn)程編程二兩種方式起線(xiàn)程和Python:使用threading模塊實(shí)現(xiàn)多線(xiàn)程編程三threading.Thread類(lèi)的重要函數(shù),這兩篇文章的示例都是演示了互不相干的獨(dú)立線(xiàn)程,,現(xiàn)在我們考慮這樣一個(gè)問(wèn)題:假設(shè)各個(gè)線(xiàn)程需要訪(fǎng)問(wèn)同一公共資源,,我們的代碼該怎么寫(xiě)?Python代碼
解決上面的問(wèn)題,,我們興許會(huì)寫(xiě)出這樣的代碼,,我們假設(shè)跑200個(gè)線(xiàn)程,但是這200個(gè)線(xiàn)程都會(huì)去訪(fǎng)問(wèn)counter這個(gè)公共資源,,并對(duì)該資源進(jìn)行處理(counter += 1),,代碼看起來(lái)就是這個(gè)樣了,但是我們看下運(yùn)行結(jié)果:
打印結(jié)果我只貼了一部分,,從中我們已經(jīng)看出了這個(gè)全局資源(counter)被搶占的情況,,問(wèn)題產(chǎn)生的原因就是沒(méi)有控制多個(gè)線(xiàn)程對(duì)同一資源的訪(fǎng)問(wèn),對(duì)數(shù)據(jù)造成破壞,,使得線(xiàn)程運(yùn)行的結(jié)果不可預(yù)期,。這種現(xiàn)象稱(chēng)為“線(xiàn)程不安全”。在開(kāi)發(fā)過(guò)程中我們必須要避免這種情況,,那怎么避免,?這就用到了我們?cè)诰C述中提到的互斥鎖了。 互斥鎖概念
Python編程中,,引入了對(duì)象互斥鎖的概念,,來(lái)保證共享數(shù)據(jù)操作的完整性。每個(gè)對(duì)象都對(duì)應(yīng)于一個(gè)可稱(chēng)為" 互斥鎖" 的標(biāo)記,,這個(gè)標(biāo)記用來(lái)保證在任一時(shí)刻,只能有一個(gè)線(xiàn)程訪(fǎng)問(wèn)該對(duì)象,。在Python中我們使用threading模塊提供的Lock類(lèi),。 我們對(duì)上面的程序進(jìn)行整改,為此我們需要添加一個(gè)互斥鎖變量mutex = threading.Lock(),,然后在爭(zhēng)奪資源的時(shí)候之前我們會(huì)先搶占這把鎖mutex.acquire(),,對(duì)資源使用完成之后我們?cè)卺尫胚@把鎖mutex.release()。代碼如下: Python代碼
同步阻塞
當(dāng)一個(gè)線(xiàn)程調(diào)用Lock對(duì)象的acquire()方法獲得鎖時(shí),,這把鎖就進(jìn)入“l(fā)ocked”狀態(tài),。因?yàn)槊看沃挥幸粋€(gè)線(xiàn)程1可以獲得鎖,所以如果此時(shí)另一個(gè)線(xiàn)程2試圖獲得這個(gè)鎖,,該線(xiàn)程2就會(huì)變?yōu)椤癰lo同步阻塞狀態(tài),。直到擁有鎖的線(xiàn)程1調(diào)用鎖的release()方法釋放鎖之后,該鎖進(jìn)入“unlocked”狀態(tài)。線(xiàn)程調(diào)度程序從處于同步阻塞狀態(tài)的線(xiàn)程中選擇一個(gè)來(lái)獲得鎖,,并使得該線(xiàn)程進(jìn)入運(yùn)行(running)狀態(tài),。 進(jìn)一步考慮
通過(guò)對(duì)公共資源使用互斥鎖,這樣就簡(jiǎn)單的到達(dá)了我們的目的,,但是如果我們又遇到下面的情況: 1,、遇到鎖嵌套的情況該怎么辦,這個(gè)嵌套是指當(dāng)我一個(gè)線(xiàn)程在獲取臨界資源時(shí),,又需要再次獲?。?/P> 2,、如果有多個(gè)公共資源,,在線(xiàn)程間共享多個(gè)資源的時(shí)候,如果兩個(gè)線(xiàn)程分別占有一部分資源并且同時(shí)等待對(duì)方的資源,; 上述這兩種情況會(huì)直接造成程序掛起,,即死鎖,下面我們會(huì)談死鎖及可重入鎖RLock,。 Python:使用threading模塊實(shí)現(xiàn)多線(xiàn)程編程五[死鎖的形成]前一篇文章Python:使用threading模塊實(shí)現(xiàn)多線(xiàn)程編程四[使用Lock互斥鎖]我們已經(jīng)開(kāi)始涉及到如何使用互斥鎖來(lái)保護(hù)我們的公共資源了,,現(xiàn)在考慮下面的情況--如果有多個(gè)公共資源,在線(xiàn)程間共享多個(gè)資源的時(shí)候,,如果兩個(gè)線(xiàn)程分別占有一部分資源并且同時(shí)等待對(duì)方的資源,,這會(huì)引起什么問(wèn)題? 死鎖概念
所謂死鎖: 是指兩個(gè)或兩個(gè)以上的進(jìn)程在執(zhí)行過(guò)程中,,因爭(zhēng)奪資源而造成的一種互相等待的現(xiàn)象,,若無(wú)外力作用,它們都將無(wú)法推進(jìn)下去,。此時(shí)稱(chēng)系統(tǒng)處于死鎖狀態(tài)或系統(tǒng)產(chǎn)生了死鎖,,這些永遠(yuǎn)在互相等待的進(jìn)程稱(chēng)為死鎖進(jìn)程。 由于資源占用是互斥的,,當(dāng)某個(gè)進(jìn)程提出申請(qǐng)資源后,,使得有關(guān)進(jìn)程在無(wú)外力協(xié)助下,永遠(yuǎn)分配不到必需的資源而無(wú)法繼續(xù)運(yùn)行,,這就產(chǎn)生了一種特殊現(xiàn)象死鎖,。 Python代碼
代碼中展示了一個(gè)線(xiàn)程的兩個(gè)功能函數(shù)分別在獲取了一個(gè)競(jìng)爭(zhēng)資源之后再次獲取另外的競(jìng)爭(zhēng)資源,我們看運(yùn)行結(jié)果:
可以看到,,程序已經(jīng)掛起在那兒了,,這種現(xiàn)象我們就稱(chēng)之為”死鎖“。 避免死鎖
避免死鎖主要方法就是:正確有序的分配資源,,避免死鎖算法中最有代表性的算法是Dijkstra E.W 于1968年提出的銀行家算法,。 Python:使用threading模塊實(shí)現(xiàn)多線(xiàn)程編程六[可重入鎖RLock]考慮這種情況:如果一個(gè)線(xiàn)程遇到鎖嵌套的情況該怎么辦,,這個(gè)嵌套是指當(dāng)我一個(gè)線(xiàn)程在獲取臨界資源時(shí),又需要再次獲取,。根據(jù)這種情況,,代碼如下: Python代碼
這種情況的代碼運(yùn)行情況如下:
之后就直接掛起了,這種情況形成了最簡(jiǎn)單的死鎖,。 那有沒(méi)有一種情況可以在某一個(gè)線(xiàn)程使用互斥鎖訪(fǎng)問(wèn)某一個(gè)競(jìng)爭(zhēng)資源時(shí),,可以再次獲取呢?在Python中為了支持在同一線(xiàn)程中多次請(qǐng)求同一資源,,python提供了“可重入鎖”:threading.RLock,。這個(gè)RLock內(nèi)部維護(hù)著一個(gè)Lock和一個(gè)counter變量,counter記錄了acquire的次數(shù),,從而使得資源可以被多次require,。直到一個(gè)線(xiàn)程所有的acquire都被release,其他的線(xiàn)程才能獲得資源,。上面的例子如果使用RLock代替Lock,,則不會(huì)發(fā)生死鎖: 代碼只需將上述的: Python代碼
替換成: Python代碼
即可。 Python:使用threading模塊實(shí)現(xiàn)多線(xiàn)程編程七[使用Condition實(shí)現(xiàn)復(fù)雜同步]目前我們已經(jīng)會(huì)使用Lock去對(duì)公共資源進(jìn)行互斥訪(fǎng)問(wèn)了,,也探討了同一線(xiàn)程可以使用RLock去重入鎖,,但是盡管如此我們只不過(guò)才處理了一些程序中簡(jiǎn)單的同步現(xiàn)象,我們甚至還不能很合理的去解決使用Lock鎖帶來(lái)的死鎖問(wèn)題,。所以我們得學(xué)會(huì)使用更深層的解決同步問(wèn)題,。Python提供的Condition對(duì)象提供了對(duì)復(fù)雜線(xiàn)程同步問(wèn)題的支持。Condition被稱(chēng)為條件變量,,除了提供與Lock類(lèi)似的acquire和release方法外,,還提供了wait和notify方法。 使用Condition的主要方式為:線(xiàn)程首先acquire一個(gè)條件變量,,然后判斷一些條件,。如果條件不滿(mǎn)足則wait;如果條件滿(mǎn)足,,進(jìn)行一些處理改變條件后,,通過(guò)notify方法通知其他線(xiàn)程,其他處于wait狀態(tài)的線(xiàn)程接到通知后會(huì)重新判斷條件,。不斷的重復(fù)這一過(guò)程,從而解決復(fù)雜的同步問(wèn)題,。 下面我們通過(guò)很著名的“生產(chǎn)者-消費(fèi)者”模型來(lái)來(lái)演示下,,在Python中使用Condition實(shí)現(xiàn)復(fù)雜同步。 Python代碼
代碼中主要實(shí)現(xiàn)了生產(chǎn)者和消費(fèi)者線(xiàn)程,,雙方將會(huì)圍繞products來(lái)產(chǎn)生同步問(wèn)題,,首先是2個(gè)生成者生產(chǎn)products ,而接下來(lái)的10個(gè)消費(fèi)者將會(huì)消耗products,代碼運(yùn)行如下:
另外:Condition對(duì)象的構(gòu)造函數(shù)可以接受一個(gè)Lock/RLock對(duì)象作為參數(shù),,如果沒(méi)有指定,,則Condition對(duì)象會(huì)在內(nèi)部自行創(chuàng)建一個(gè)RLock;除了notify方法外,,Condition對(duì)象還提供了notifyAll方法,,可以通知waiting池中的所有線(xiàn)程嘗試acquire內(nèi)部鎖。由于上述機(jī)制,,處于waiting狀態(tài)的線(xiàn)程只能通過(guò)notify方法喚醒,,所以notifyAll的作用在于防止有線(xiàn)程永遠(yuǎn)處于沉默狀態(tài)。 Python:使用threading模塊實(shí)現(xiàn)多線(xiàn)程編程八[使用Event實(shí)現(xiàn)線(xiàn)程間通信]使用threading.Event可以實(shí)現(xiàn)線(xiàn)程間相互通信,,之前的Python:使用threading模塊實(shí)現(xiàn)多線(xiàn)程編程七[使用Condition實(shí)現(xiàn)復(fù)雜同步]我們已經(jīng)初步實(shí)現(xiàn)了線(xiàn)程間通信的基本功能,,但是更為通用的一種做法是使用threading.Event對(duì)象。使用threading.Event可以使一個(gè)線(xiàn)程等待其他線(xiàn)程的通知,,我們把這個(gè)Event傳遞到線(xiàn)程對(duì)象中,,Event默認(rèn)內(nèi)置了一個(gè)標(biāo)志,初始值為False,。一旦該線(xiàn)程通過(guò)wait()方法進(jìn)入等待狀態(tài),,直到另一個(gè)線(xiàn)程調(diào)用該Event的set()方法將內(nèi)置標(biāo)志設(shè)置為T(mén)rue時(shí),該Event會(huì)通知所有等待狀態(tài)的線(xiàn)程恢復(fù)運(yùn)行,。 Python代碼
運(yùn)行效果如下:
|
|
來(lái)自: A_Geek > 《面向?qū)ο髉ython》