線程和內(nèi)核對象的同步_第1頁
線程和內(nèi)核對象的同步_第2頁
線程和內(nèi)核對象的同步_第3頁
線程和內(nèi)核對象的同步_第4頁
線程和內(nèi)核對象的同步_第5頁
已閱讀5頁,還剩42頁未讀 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)

文檔簡介

1、第9章 線程與內(nèi)核對象的同步上一章介紹了如何使用允許線程保留在用戶方式中的機(jī)制來實現(xiàn)線程同步的方法。用戶方式同步的優(yōu)點是它的同步速度非???。如果強(qiáng)調(diào)線程的運行速度,那么首先應(yīng)該確定用戶方式的線程同步機(jī)制是否適合需要。雖然用戶方式的線程同步機(jī)制具有速度快的優(yōu)點,但是它也有其局限性。對于許多應(yīng)用程序來說,這種機(jī)制是不適用的。例如,互鎖函數(shù)家族只能在單值上運行,根本無法使線程進(jìn)入等待狀態(tài)??梢允褂藐P(guān)鍵代碼段使線程進(jìn)入等待狀態(tài),但是只能用這些代碼段對單個進(jìn)程中的線程實施同步。還有,使用關(guān)鍵代碼段時,很容易陷入死鎖狀態(tài),因為在等待進(jìn)入關(guān)鍵代碼段時無法設(shè)定超時值。本章將要介紹如何使用內(nèi)核對象來實現(xiàn)線程的同

2、步。你將會看到,內(nèi)核對象機(jī)制的適應(yīng)性遠(yuǎn)遠(yuǎn)優(yōu)于用戶方式機(jī)制。實際上,內(nèi)核對象機(jī)制的唯一不足之處是它的速度比較慢。當(dāng)調(diào)用本章中提到的任何新函數(shù)時,調(diào)用線程必須從用戶方式轉(zhuǎn)為內(nèi)核方式。這個轉(zhuǎn)換需要很大的代價:往返一次需要占用x 8 6平臺上的大約1 0 0 0個C P U周期,當(dāng)然,這還不包括執(zhí)行內(nèi)核方式代碼,即實現(xiàn)線程調(diào)用的函數(shù)的代碼所需的時間。本書介紹了若干種內(nèi)核對象,包括進(jìn)程,線程和作業(yè)??梢詫⑺羞@些內(nèi)核對象用于同步目的。對于線程同步來說,這些內(nèi)核對象中的每種對象都可以說是處于已通知或未通知的狀態(tài)之中。這種狀態(tài)的切換是由M i c r o s o f t為每個對象建立的一套規(guī)則來決定的。例如

3、,進(jìn)程內(nèi)核對象總是在未通知狀態(tài)中創(chuàng)建的。當(dāng)進(jìn)程終止運行時,操作系統(tǒng)自動使該進(jìn)程的內(nèi)核對象處于已通知狀態(tài)。一旦進(jìn)程內(nèi)核對象得到通知,它將永遠(yuǎn)保持這種狀態(tài),它的狀態(tài)永遠(yuǎn)不會改為未通知狀態(tài)。當(dāng)進(jìn)程正在運行的時候,進(jìn)程內(nèi)核對象處于未通知狀態(tài),當(dāng)進(jìn)程終止運行的時候,它就變?yōu)橐淹ㄖ獱顟B(tài)。進(jìn)程內(nèi)核對象中是個布爾值,當(dāng)對象創(chuàng)建時,該值被初始化為FA L S E(未通知狀態(tài))。當(dāng)進(jìn)程終止運行時,操作系統(tǒng)自動將對應(yīng)的對象布爾值改為T R U E,表示該對象已經(jīng)得到通知。如果編寫的代碼是用于檢查進(jìn)程是否仍在運行,那么只需要調(diào)用一個函數(shù),讓操作系統(tǒng)去檢查進(jìn)程對象的布爾值,這非常簡單。你也可能想要告訴系統(tǒng)使線程進(jìn)入等待

4、狀態(tài),然后當(dāng)布爾值從FA L S E改為T R U E時自動喚醒該線程。這樣,你可以編寫一個代碼,在這個代碼中,需要等待子進(jìn)程終止運行的父進(jìn)程中的線程只需要使自己進(jìn)入睡眠狀態(tài),直到標(biāo)識子進(jìn)程的內(nèi)核對象變?yōu)橐淹ㄖ獱顟B(tài)即可。你將會看到, M i c r o s o f t的Wi n d o w s提供了一些能夠非常容易地完成這些操作的函數(shù)。剛才講了M i c r o s o f t為進(jìn)程內(nèi)核對象定義了一些規(guī)則。實際上,線程內(nèi)核對象也遵循同樣的規(guī)則。即線程內(nèi)核對象總是在未通知狀態(tài)中創(chuàng)建。當(dāng)線程終止運行時,操作系統(tǒng)會自動將線程對象的狀態(tài)改為已通知狀態(tài)。因此,可以將相同的方法用于應(yīng)用程序,以確定線程是否

5、不再運行。與進(jìn)程內(nèi)核對象一樣,線程內(nèi)核對象也可以處于已通知狀態(tài)或未通知狀態(tài)。下面的內(nèi)核對象可以處于已通知狀態(tài)或未通知狀態(tài): 進(jìn)程 文件修改通知 線程 事件 作業(yè) 可等待定時器 文件 信標(biāo) 控制臺輸入 互斥對象線程可以使自己進(jìn)入等待狀態(tài),直到一個對象變?yōu)橐淹ㄖ獱顟B(tài)。注意,用于控制每個對象的已通知/未通知狀態(tài)的規(guī)則要根據(jù)對象的類型而定。前面已經(jīng)提到進(jìn)程和線程對象的規(guī)則及作業(yè)的規(guī)則。本章將要介紹允許線程等待某個內(nèi)核對象變?yōu)橐淹ㄖ獱顟B(tài)所用的函數(shù)。然后我們將要講述Wi n d o w s提供的專門用來幫助實現(xiàn)線程同步的各種內(nèi)核對象、如事件、等待計數(shù)器,信標(biāo)和互斥對象。當(dāng)我最初開始學(xué)習(xí)這項內(nèi)容時,我設(shè)想內(nèi)

6、核對象包含了一面旗幟(在空中飄揚(yáng)的旗幟,不是耷拉下來的旗幟),這對我很有幫助。當(dāng)內(nèi)核對象得到通知時,旗幟升起來;當(dāng)對象未得到通知時,旗幟就降下來(見圖9 - 1)。當(dāng)線程等待的對象處于未通知狀態(tài)(旗幟降下)中時,這些線程不可調(diào)度。但是一旦對象變?yōu)橐淹ㄖ獱顟B(tài)(旗幟升起),線程看到該標(biāo)志變?yōu)榭烧{(diào)度狀態(tài),并且很快恢復(fù)運行(見圖9 - 2)。圖9-1 內(nèi)核對象中的旗幟狀態(tài)圖9-2 內(nèi)核對象中旗幟狀態(tài)與線程可調(diào)度性示意圖9.1 等待函數(shù)等待函數(shù)可使線程自愿進(jìn)入等待狀態(tài),直到一個特定的內(nèi)核對象變?yōu)橐淹ㄖ獱顟B(tài)為止。這些等待函數(shù)中最常用的是Wa i t F o r S i n g l e O b j e c

7、t :DWORD WaitForSingleObject(HANDLE hObject, DWORD dwMilliseconds);當(dāng)線程調(diào)用該函數(shù)時,第一個參數(shù)h O b j e c t標(biāo)識一個能夠支持被通知/未通知的內(nèi)核對象(前面列出的任何一種對象都適用)。第二個參數(shù)d w M i l l i s e c o n d s允許該線程指明,為了等待該對象變?yōu)橐淹ㄖ獱顟B(tài),它將等待多長時間。調(diào)用下面這個函數(shù)將告訴系統(tǒng),調(diào)用函數(shù)準(zhǔn)備等待到h P r o c e s s句柄標(biāo)識的進(jìn)程終止運行為止:WaitForSingleObject(hProcess, INFINITE);第二個參數(shù)告訴系統(tǒng),調(diào)用

8、線程愿意永遠(yuǎn)等待下去(無限時間量),直到該進(jìn)程終止運行。通常情況下, I N F I N I T E是作為第二個參數(shù)傳遞給Wa i t F o r S i n g l e O b j e c t的,不過也可以傳遞任何一個值(以毫秒計算)。順便說一下, I N F I N I T E已經(jīng)定義為0 x F F F F F F F F(或-1)。當(dāng)然,傳遞I N F I N I T E有些危險。如果對象永遠(yuǎn)不變?yōu)橐淹ㄖ獱顟B(tài),那么調(diào)用線程永遠(yuǎn)不會被喚醒,它將永遠(yuǎn)處于死鎖狀態(tài),不過,它不會浪費寶貴的C P U時間。下面是如何用一個超時值而不是I N F I N I T E來調(diào)用Wa i t F o r

9、S i n g l e O b j e c t的例子:DWORD dw = WaitForSingleObject(hProcess, 5000);switch(dw) case WAIT_OBJECT_0: / The process terminated. break; case WAIT_TIMEOUT: / The process did not terminate within 5000 milliseconds. break; case WAIT_FAILED: / Bad call to function (invalid handle?) break;上面這個代碼告訴系統(tǒng),在特

10、定的進(jìn)程終止運行之前,或者在5 0 0 0 m s時間結(jié)束之前,調(diào)用線程不應(yīng)該變?yōu)榭烧{(diào)度狀態(tài)。因此,如果進(jìn)程終止運行,那么這個函數(shù)調(diào)用將在不到5 0 0 0 m s的時間內(nèi)返回,如果進(jìn)程尚未終止運行,那么它在大約5 0 0 0 m s時間內(nèi)返回。注意,不能為d w M i l l i s e c o n d傳遞0。如果傳遞了0,Wa i t F o r S i n g l e O b j e c t函數(shù)將總是立即返回。Wa i t F o r S i n g l e O b j e c t的返回值能夠指明調(diào)用線程為什么再次變?yōu)榭烧{(diào)度狀態(tài)。如果線程等待的對象變?yōu)橐淹ㄖ獱顟B(tài),那么返回值是WA I

11、T _ O B J E C T _ 0。如果設(shè)置的超時已經(jīng)到期,則返回值是WA I T _ T I M E O U T。如果將一個錯誤的值(如一個無效句柄)傳遞給Wa i t F o r S i n g l eO b j e c t,那么返回值將是WA I T _ FA I L E D(若要了解詳細(xì)信息,可調(diào)用G e t L a s t E r r o r)。下面這個函數(shù)Wa i t F o r M u l t i p l e O b j e c t s與Wa i t F o r S i n g l e O b j e c t函數(shù)很相似,區(qū)別在于它允許調(diào)用線程同時查看若干個內(nèi)核對象的已通知狀態(tài)

12、:DWORD WaitForMultipleObjects(DWORD dwCount, CONST HANDLE* phObjects, BOOL fWaitAll, DWORD dwMilliseconds);d w C o u n t參數(shù)用于指明想要讓函數(shù)查看的內(nèi)核對象的數(shù)量。這個值必須在1與M A X I M U M _WA I T _ O B J E C T S(在Wi n d o w s頭文件中定義為6 4)之間。p h O b j e c t s參數(shù)是指向內(nèi)核對象句柄的數(shù)組的指針??梢砸詢煞N不同的方式來使用Wa i t F o r M u l t i p l e O b j e

13、c t s函數(shù)。一種方式是讓線程進(jìn)入等待狀態(tài),直到指定內(nèi)核對象中的任何一個變?yōu)橐淹ㄖ獱顟B(tài)。另一種方式是讓線程進(jìn)入等待狀態(tài),直到所有指定的內(nèi)核對象都變?yōu)橐淹ㄖ獱顟B(tài)。f Wa i tAl l參數(shù)告訴該函數(shù),你想要讓它使用何種方式。如果為該參數(shù)傳遞T R U E,那么在所有對象變?yōu)橐淹ㄖ獱顟B(tài)之前,該函數(shù)將不允許調(diào)用線程運行。d w M i l l i s e c o n d s參數(shù)的作用與它在Wa i t F o r S i n g l e O b j e c t中的作用完全相同。如果在等待的時候規(guī)定的時間到了,那么該函數(shù)無論如何都會返回。同樣,通常為該參數(shù)傳遞I N F I N I T E,但是在

14、編寫代碼時應(yīng)該小心,以避免出現(xiàn)死鎖情況。Wa i t F o r M u l t i p l e O b j e c t s函數(shù)的返回值告訴調(diào)用線程,為什么它會被重新調(diào)度??赡艿姆祷刂凳荳A I T _ FA I L E D和WA I T _ T I M E O U T,這兩個值的作用是很清楚的。如果為f Wa i tAl l參數(shù)傳遞T R U E,同時所有對象均變?yōu)橐淹ㄖ獱顟B(tài),那么返回值是WA I T _ O B J E C T _ 0。如果為f Wa i t A l l傳遞FA L S E,那么一旦任何一個對象變?yōu)橐淹ㄖ獱顟B(tài),該函數(shù)便返回。在這種情況下,你可能想要知道哪個對象變?yōu)橐淹ㄖ獱顟B(tài)

15、。返回值是WA I T _ O B J E C T _ 0與(WA I T _ O B J E C T _ 0 + d w C o u n t - 1)之間的一個值。換句話說,如果返回值不是WA I T _ T I M E O U T,也不是WA I T _ FA I L E D,那么應(yīng)該從返回值中減去WA I T _ O B J E C T _ 0。產(chǎn)生的數(shù)字是作為第二個參數(shù)傳遞給Wa i t F o r M u l t i p l e O b j e c t s的句柄數(shù)組中的索引。該索引說明哪個對象變?yōu)橐淹ㄖ獱顟B(tài)。下面是說明這一情況的一些示例代碼:HANDLE h3;h0 = hProce

16、ss1;h1 = hProcess2;h2 = hProcess3;DWORD dw = WaitForMultipleObjects(3, h, FALSE, 5000);switch(dw) case WAIT_FAILED: / Bad call to function (invalid handle?) break; case WAIT_TIMEOUT: / None of the objects became signaled within 5000 milliseconds. break; case WAIT_OBJECT_0 + 0: / The process identifi

17、ed by h0 (hProcess1) terminated. break; case WAIT_OBJECT_0 + 1: / The process identified by h1 (hProcess2) terminated. break; case WAIT_OBJECT_0 + 2: / The process identified by h2 (hProcess3) terminated. break;如果為f Wa i t A l l參數(shù)傳遞FA L S E,Wa i t F o r M u l t i p l e O b j e c t s就從索引0開始向上對句柄數(shù)組進(jìn)行掃

18、描,同時已通知的第一個對象終止等待狀態(tài)。這可能產(chǎn)生一些你不希望有的結(jié)果。例如,通過將3個進(jìn)程句柄傳遞給該函數(shù),你的線程就會等待3個子進(jìn)程終止運行。如果數(shù)組中索引為0的進(jìn)程終止運行,Wa i t F o r M u l t i p l e O b j e c t s就會返回。這時該線程就可以做它需要的任何事情,然后循環(huán)反復(fù),等待另一個進(jìn)程終止運行。如果該線程傳遞相同的3個句柄,該函數(shù)立即再次返回WA I T _ O B J E C T _ 0。除非刪除已經(jīng)收到通知的句柄,否則代碼就無法正確地運行。9.2 成功等待的副作用對于有些內(nèi)核對象來說,成功地調(diào)用Wa i t F o r S i n g l

19、 e O b j e c t和Wa i t F o r M u l t i p l e O b j e c t s,實際上會改變對象的狀態(tài)。成功地調(diào)用是指函數(shù)發(fā)現(xiàn)對象已經(jīng)得到通知并且返回一個相對于WA I T _ O B J E C T _ 0的值。如果函數(shù)返回WA I T _ T I M E O U T或WA I T _ FA I L E D,那么調(diào)用就沒有成功。如果函數(shù)調(diào)用沒有成功,對象的狀態(tài)就不可能改變。當(dāng)一個對象的狀態(tài)改變時,我稱之為成功等待的副作用。例如,有一個線程正在等待自動清除事件對象(本章后面將要介紹)。當(dāng)事件對象變?yōu)橐淹ㄖ獱顟B(tài)時,函數(shù)就會發(fā)現(xiàn)這個情況,并將WA I T _ O

20、 B J E C T _ 0返回給調(diào)用線程。但是就在函數(shù)返回之前,該事件將被置為未通知狀態(tài),這就是成功等待的副作用。這個副作用將用于自動清除內(nèi)核對象,因為它是M i c r o s o f t為這種類型的對象定義的規(guī)則之一。其他對象擁有不同的副作用,而有些對象則根本沒有任何副作用。進(jìn)程和線程內(nèi)核對象就根本沒有任何副作用,也就是說,在這些對象之一上進(jìn)行等待決不會改變對象的狀態(tài)。由于本章要介紹各種不同的內(nèi)核對象,因此我們將要詳細(xì)說明它們的成功等待的副作用。究竟是什么原因使得Wa i t F o r M u l t i p l e O b j e c t s函數(shù)如此有用呢,因為它能夠以原子操作方式來

21、執(zhí)行它的所有操作。當(dāng)一個線程調(diào)用Wa i t F o r M u l t i p l e O b j e c t s函數(shù)時,該函數(shù)能夠測試所有對象的通知狀態(tài),并且能夠?qū)⑺斜匾母弊饔米鳛橐豁棽僮鱽韴?zhí)行。讓我們觀察一個例子。兩個線程以完全相同的方式來調(diào)用Wa i t F o r M u l t i p l e O b j e c t s:HANDLE h2;h0 = hAutoResetEvent1; / Initially nonsignaledh1 = hAutoResetEvent2; / Initially nonsignaledWaitForMultipleObjects(2, h,

22、 TRUE, INFINITE);當(dāng)Wa i t F o r M u l t i p l e O b j e c t s函數(shù)被調(diào)用時,兩個事件都處于未通知狀態(tài),這就迫使兩個線程都進(jìn)入等待狀態(tài)。然后h A u t o R e s e t E v e n t 1對象變?yōu)橐淹ㄖ獱顟B(tài)。兩個線程都發(fā)現(xiàn),該事件已經(jīng)變?yōu)橐淹ㄖ獱顟B(tài),但是它們都無法被喚醒,因為h A u t o R e s e t E v e n t 2仍然處于未通知狀態(tài)。由于兩個線程都沒有等待成功,因此沒有對h A u t o R e s e t E v e n t 1對象產(chǎn)生任何副作用。接著,h A u t o R e s e t E v

23、 e n t 2變?yōu)橐淹ㄖ獱顟B(tài)。這時,兩個線程中的一個發(fā)現(xiàn),兩個對象都變?yōu)橐淹ㄖ獱顟B(tài)。等待取得了成功,兩個事件對象均被置為未通知狀態(tài),該線程變?yōu)榭烧{(diào)度的線程。但是另一個線程的情況如何呢?它將繼續(xù)等待,直到它發(fā)現(xiàn)兩個事件對象都處于已通知狀態(tài)。盡管它原先發(fā)現(xiàn)h A u t o R e s e t E v e n t 1處于已通知狀態(tài),但是現(xiàn)在它將該對象視為未通知狀態(tài)。前面講過,有一個重要問題必須注意,即Wa i t F o r M u l t i p l e O b j e c t s是以原子操作方式運行的。當(dāng)它檢查內(nèi)核對象的狀態(tài)時,其他任何線程都無法背著對象改變它的狀態(tài)。這可以防止出現(xiàn)死鎖情況。

24、試想,如果一個線程看到h A u t o R e s e t E v e n t 1已經(jīng)得到通知并將事件重置為未通知狀態(tài),然后,另一個線程發(fā)現(xiàn)h A u t o R e s e t E v e n t 2已經(jīng)得到通知并將該事件重置為未通知狀態(tài),那么這兩個線程均將被凍結(jié):一個線程將等待另一個線程已經(jīng)得到的對象,另一個線程將等待該線程已經(jīng)得到的對象。Wa i t F o r M u l t i p l e O b j e c t s能夠確保這種情況永遠(yuǎn)不會發(fā)生。這會產(chǎn)生一個非常有趣的問題,即如果多個線程等待單個內(nèi)核對象,那么當(dāng)該對象變成已通知狀態(tài)時,系統(tǒng)究竟決定喚醒哪個線程呢? M i c r o

25、 s o f t對這個問題的正式回答是:“算法是公平的。”M i c r o s o f t不想使用系統(tǒng)使用的內(nèi)部算法。它只是說該算法是公平的,這意味著如果多個線程正在等待,那么每當(dāng)對象變?yōu)橐淹ㄖ獱顟B(tài)時,每個線程都應(yīng)該得到它自己的被喚醒的機(jī)會。這意味著線程的優(yōu)先級不起任何作用,即高優(yōu)先級線程不一定得到該對象。這還意味著等待時間最長的線程不一定得到該對象。同時得到對象的線程有可能反復(fù)循環(huán),并且再次得到該對象。但是,這對于其他線程來說是不公平的,因此該算法將設(shè)法防止這種情況的出現(xiàn)。但是這不一定做得到。在實際操作中, M i c r o s o f t使用的算法是常用的“先進(jìn)先出”的方案。等待了最長

26、時間的線程將得到該對象。但是系統(tǒng)中將會執(zhí)行一些操作,以便改變這個行為特性,使它不太容易預(yù)測。這就是為什么M i c r o s o f t沒有明確說明該算法如何起作用的原因。操作之一是讓線程暫停運行。如果一個線程等待一個對象,然后該線程暫停運行,那么系統(tǒng)就會忘記該線程正在等待該對象。這是一個特性,因為沒有理由為一個暫停運行的線程進(jìn)行調(diào)度。當(dāng)后來該線程恢復(fù)運行時,系統(tǒng)將認(rèn)為該線程剛剛開始等待該對象。當(dāng)調(diào)試一個進(jìn)程時,只要到達(dá)一個斷點,該進(jìn)程中的所有線程均暫停運行。因此,調(diào)試一個進(jìn)程會使“先進(jìn)先出”的算法很難預(yù)測其結(jié)果,因為線程常常暫停運行,然后再恢復(fù)運行。9.3 事件內(nèi)核對象在所有的內(nèi)核對象中,

27、事件內(nèi)核對象是個最基本的對象。它們包含一個使用計數(shù)(與所有內(nèi)核對象一樣),一個用于指明該事件是個自動重置的事件還是一個人工重置的事件的布爾值,另一個用于指明該事件處于已通知狀態(tài)還是未通知狀態(tài)的布爾值。事件能夠通知一個操作已經(jīng)完成。有兩種不同類型的事件對象。一種是人工重置的事件,另一種是自動重置的事件。當(dāng)人工重置的事件得到通知時,等待該事件的所有線程均變?yōu)榭烧{(diào)度線程。當(dāng)一個自動重置的事件得到通知時,等待該事件的線程中只有一個線程變?yōu)榭烧{(diào)度線程。當(dāng)一個線程執(zhí)行初始化操作,然后通知另一個線程執(zhí)行剩余的操作時,事件使用得最多。事件初始化為未通知狀態(tài),然后,當(dāng)該線程完成它的初始化操作后,它就將事件設(shè)置為

28、已通知狀態(tài)。這時,一直在等待該事件的另一個線程發(fā)現(xiàn)該事件已經(jīng)得到通知,因此它就變成可調(diào)度線程。這第二個線程知道第一個線程已經(jīng)完成了它的操作。下面是C r e a t e E v e n t函數(shù),用于創(chuàng)建事件內(nèi)核對象:HANDLE CreateEvent( PSECURITY_ATTRIBUTES psa, BOOL fManualReset, BOOL fInitialState, PCTSTR pszName);第3章已經(jīng)介紹了內(nèi)核對象的操作技巧,比如,如何設(shè)置它們的安全性,如何進(jìn)行使用計數(shù),如何繼承它們的句柄,如何按名字共享對象等。由于現(xiàn)在你對所有這些對象都已經(jīng)熟悉了,所以不再介紹該函數(shù)的

29、第一個和最后一個參數(shù)。F M a n n u a l R e s e t參數(shù)是個布爾值,它能夠告訴系統(tǒng)是創(chuàng)建一個人工重置的事件( T R U E)還是創(chuàng)建一個自動重置的事件( FA L S E)。f I n i t i a l S t a t e參數(shù)用于指明該事件是要初始化為已通知狀態(tài)(T R U E)還是未通知狀態(tài)(FA L S E)。當(dāng)系統(tǒng)創(chuàng)建事件對象后, c r e a t e E v e n t就將與進(jìn)程相關(guān)的句柄返回給事件對象。其他進(jìn)程中的線程可以獲得對該對象的訪問權(quán),方法是使用在p s z N a m e參數(shù)中傳遞的相同值,使用繼承性,使用D u p l i c a t e H a

30、 n d l e函數(shù)等來調(diào)用C r e a t e E v e n t,或者調(diào)用O p e n E v e n t ,在p s z N a m e參數(shù)中設(shè)定一個與調(diào)用C r e a t e E v e n t時設(shè)定的名字相匹配的名字:HANDLE OpenEvent( DWORD fdwAccess, BOOL fInherit, PCTSTR pszName);與所有情況中一樣,當(dāng)不再需要事件內(nèi)核對象時,應(yīng)該調(diào)用C l o s e H a n d l e函數(shù)。一旦事件已經(jīng)創(chuàng)建,就可以直接控制它的狀態(tài)。當(dāng)調(diào)用S e t E v e n t時,可以將事件改為已通知狀態(tài):BOOL SetEvent

31、(HANDLE hEvent);當(dāng)調(diào)用R e s e t E v e n t函數(shù)時,可以將該事件改為未通知狀態(tài):BOOL ResetEvent(HANDLE hEvent);就是這么容易。M i c r o s o f t為自動重置的事件定義了應(yīng)該成功等待的副作用規(guī)則,即當(dāng)線程成功地等待到該對象時,自動重置的事件就會自動重置到未通知狀態(tài)。這就是自動重置的事件如何獲得它們的名字的方法。通常沒有必要為自動重置的事件調(diào)用R e s e t E v e n t函數(shù),因為系統(tǒng)會自動對事件進(jìn)行重置。但是, M i c r o s o f t沒有為人工重置的事件定義成功等待的副作用。讓我們觀察一個簡單的例子

32、,以便說明如何使用事件內(nèi)核對象對線程進(jìn)行同步。下面就是這個代碼:/ Create a global handle to a manual-reset, nonsignaled event.HANDLE g_hEvent;int WINAPI WinMain(.) /Create the manual-reset, nonsignaled event. g_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); /Spawn 3 new threads. HANDLE hThread3; DWORD dwThreadID; hThread0 = _begin

33、threadex(NULL, 0, WordCount, NULL, 0, &dwThreadID); hThread1 = _beginthreadex(NULL, 0, SpellCheck, NULL, 0, &dwThreadID); hThread2 = _beginthreadex(NULL, 0, GrammarCheck, NULL, 0, &dwThreadID); OpenFileAndReadContentsIntoMemory(.); /Allow all 3 threads to access the memory. SetEvent(g_hEvent); .DWOR

34、D WINAPI WordCount(PVOID pvParam) /Wait until the files data is in memory. WaitForSingleObject(g_hEvent, INFINITE); /Access the memory block. . return(0);DWORD WINAPI SpellCheck(PVOID pvParam) /Wait until the files data is in memory. WaitForSingleObject(g_hEvent, INFINITE); /Access the memory block.

35、 . return(0);DWORD WINAPI GrammarCheck(PVOID pvParam) /Wait until the files data is in memory. WaitForSingleObject(g_hEvent, INFINITE); /Access the memory block. . return(0);當(dāng)這個進(jìn)程啟動時,它創(chuàng)建一個人工重置的未通知狀態(tài)的事件,并且將句柄保存在一個全局變量中。這使得該進(jìn)程中的其他線程能夠非常容易地訪問同一個事件對象?,F(xiàn)在3個線程已經(jīng)產(chǎn)生。這些線程要等待文件的內(nèi)容讀入內(nèi)存,然后每個線程都要訪問它的數(shù)據(jù)。一個線程進(jìn)行單詞計數(shù)

36、,另一個線程運行拼寫檢查器,第三個線程運行語法檢查器。這3個線程函數(shù)的代碼的開始部分都相同,每個函數(shù)都調(diào)用Wa i t F o r S i n g l e O b j e c t,這將使線程暫停運行,直到文件的內(nèi)容由主線程讀入內(nèi)存為止。一旦主線程將數(shù)據(jù)準(zhǔn)備好,它就調(diào)用S e t E v e n t,給事件發(fā)出通知信號。這時,系統(tǒng)就使所有這3個輔助線程進(jìn)入可調(diào)度狀態(tài),它們都獲得了C P U時間,并且可以訪問內(nèi)存塊。注意,這3個線程都以只讀方式訪問內(nèi)存。這就是所有3個線程能夠同時運行的唯一原因。還要注意,如何計算機(jī)上配有多個C P U,那么所有3個線程都能夠真正地同時運行,從而可以在很短的時間內(nèi)完

37、成大量的操作。如果你使用自動重置的事件而不是人工重置的事件,那么應(yīng)用程序的行為特性就有很大的差別。當(dāng)主線程調(diào)用S e t E v e n t之后,系統(tǒng)只允許一個輔助線程變成可調(diào)度狀態(tài)。同樣,也無法保證系統(tǒng)將使哪個線程變?yōu)榭烧{(diào)度狀態(tài)。其余兩個輔助線程將繼續(xù)等待。已經(jīng)變?yōu)榭烧{(diào)度狀態(tài)的線程擁有對內(nèi)存塊的獨占訪問權(quán)。讓我們重新編寫線程的函數(shù),使得每個函數(shù)在返回前調(diào)用S e t E v e n t函數(shù)(就像Wi n M a i n函數(shù)所做的那樣)。這些線程函數(shù)現(xiàn)在變成下面的形式:DWORD WINAPI WordCount(PVOID pvParam) /Wait until the files dat

38、a is in memory. WaitForSingleObject(g_hEvent, INFINITE); /Access the memory block. . SetEvent(g_hEvent); return(0);DWORD WINAPI SpellCheck(PVOID pvParam) /Wait until the files data is in memory. WaitForSingleObject(g_hEvent, INFINITE); /Access the memory block. . SetEvent(g_hEvent); return(0);DWORD

39、WINAPI GrammarCheck(PVOID pvParam) /Wait until the files data is in memory. WaitForSingleObject(g_hEvent, INFINITE); /Access the memory block. . SetEvent(g_hEvent); return(0);當(dāng)線程完成它對數(shù)據(jù)的專門傳遞時,它就調(diào)用S e t E v e n t函數(shù),該函數(shù)允許系統(tǒng)使得兩個正在等待的線程中的一個成為可調(diào)度線程。同樣,我們不知道系統(tǒng)將選擇哪個線程作為可調(diào)度線程,但是該線程將進(jìn)行它自己的對內(nèi)存塊的專門傳遞。當(dāng)該線程完成操作時,

40、它也將調(diào)用S e t E v e n t函數(shù),使第三個即最后一個線程進(jìn)行它自己的對內(nèi)存塊的傳遞。注意,當(dāng)使用自動重置事件時,如果每個輔助線程均以讀/寫方式訪問內(nèi)存塊,那么就不會產(chǎn)生任何問題,這些線程將不再被要求將數(shù)據(jù)視為只讀數(shù)據(jù)。這個例子清楚地展示出使用人工重置事件與自動重置事件之間的差別。為了完整起見,下面再介紹一個可以用于事件的函數(shù):BOOL PulseEvent(HANDLE hEvent);P u l s e E v e n t函數(shù)使得事件變?yōu)橐淹ㄖ獱顟B(tài),然后立即又變?yōu)槲赐ㄖ獱顟B(tài),這就像在調(diào)用S e t E v e n t后又立即調(diào)用R e s e t E v e n t函數(shù)一樣。如果

41、在人工重置的事件上調(diào)用P u l s e E v e n t函數(shù),那么在發(fā)出該事件時,等待該事件的任何一個線程或所有線程將變?yōu)榭烧{(diào)度線程。如果在自動重置事件上調(diào)用P u l s e E v e n t函數(shù),那么只有一個等待該事件的線程變?yōu)榭烧{(diào)度線程。如果在發(fā)出事件時沒有任何線程在等待該事件,那么將不起任何作用。P u l s e E v e n t函數(shù)并不非常有用。實際上我在自己的應(yīng)用程序中從未使用它,因為根本不知道什么線程將會看到事件的發(fā)出并變成可調(diào)度線程。由于在調(diào)用P u l s e E v e n t時無法知道任何線程的狀態(tài),因此該函數(shù)并不那么有用。我相信在有些情況下,雖然P u l s

42、 e E v e n t函數(shù)可以方便地供你使用,但是你根本想不起要去使用它。關(guān)于P u l s e E v e n t函數(shù)的比較詳細(xì)的說明,請參見本章后面對S i n g l e O b j e c t A n d Wa i t函數(shù)的介紹。H a n d s h a k e示例應(yīng)用程序清單9 - 1中列出了H a n d s h a k e(“09 Handshake.exe”)應(yīng)用程序,它展示了自動重置事件的使用情況。該應(yīng)用程序的源代碼文件和資源文件均在本書所附光盤上的0 9 - H a n d s h a k e目錄下。當(dāng)運行H a n d s h a k e應(yīng)用程序時,就會出現(xiàn)圖9 -

43、3所示的對話框。H a n d s h a k e應(yīng)用程序接受一個請求字符串,再將該字符串中的所有字符反轉(zhuǎn),然后將結(jié)果放入R e s u l t域。H a n d s h a k e應(yīng)用程序的出色之處在于它完成這個重要任務(wù)時所用的方法不同一般。H a n d s h a k e能夠解決常見的編程問題。現(xiàn)在有一個客戶機(jī)和一個服務(wù)器,它們之間需要互相進(jìn)行通信。開始時,服務(wù)器無事可做,因此它進(jìn)入等待狀態(tài)。當(dāng)客戶機(jī)準(zhǔn)備將一個請求提交給服務(wù)器時,它將該請求放入一個共享內(nèi)存緩沖區(qū)中,然后發(fā)出一個事件通知,這樣,服務(wù)器線程就會知道查看數(shù)據(jù)緩沖區(qū)并處理客戶機(jī)的請求。當(dāng)服務(wù)器線程忙于處理該請求的時候,客戶機(jī)的線

44、程必須進(jìn)入等待狀態(tài),直到服務(wù)器準(zhǔn)備好請求的結(jié)果為止。因此客戶機(jī)進(jìn)入等待狀態(tài),直到服務(wù)器發(fā)出另一個事件通知,指明結(jié)果已經(jīng)準(zhǔn)備好,可供客戶機(jī)進(jìn)行處理。當(dāng)客戶機(jī)再次被喚醒的時候,它就知道結(jié)果已經(jīng)放入共享數(shù)據(jù)緩沖區(qū)中,并且可以將結(jié)果顯示給用戶。圖9-3 Handshake 對話框當(dāng)該應(yīng)用程序啟動運行時,它立即創(chuàng)建兩個未通知的自動重置的事件對象。一個事件是g _h e v t R e q u e s t S u b m i t t e d,用于指明何時為服務(wù)器準(zhǔn)備一個請求。該事件由服務(wù)器線程等待,并由客戶機(jī)線程發(fā)出通知。第二個事件是g _ h e v t R e s u l t R e t u r n

45、e d,用來指明何時為客戶機(jī)準(zhǔn)備好結(jié)果??蛻魴C(jī)線程等待該事件,而服務(wù)器線程則負(fù)責(zé)發(fā)出該事件的通知。當(dāng)各個事件創(chuàng)建后,服務(wù)器線程就產(chǎn)生并且執(zhí)行S e r v e r T h r e a d函數(shù)。該函數(shù)立即讓服務(wù)器等待客戶機(jī)的請求。與此同時,主線程(它也是客戶機(jī)線程)調(diào)用D i a l o g B o x函數(shù),該函數(shù)負(fù)責(zé)顯示應(yīng)用程序的用戶界面??梢詫⒁恍┪淖州斎隦 e q u e s t域,然后,當(dāng)點擊Submit Request ToS e r v e r (將請求提交給服務(wù)器)時,請求字符串將被放入由客戶機(jī)和服務(wù)器線程共享的一個緩沖區(qū),并發(fā)出g _ h e v t R e q u e s t

46、S u b m i t t e d事件的通知。然后客戶機(jī)線程通過等待g _ h e v t R e s u l t R e t u r e n e d事件來等待服務(wù)器的結(jié)果。服務(wù)器醒來,將共享內(nèi)存緩沖區(qū)中的字符串反轉(zhuǎn),然后發(fā)出g _ h e v t R e s u l t R e t u r n e d事件的通知。服務(wù)器的線程循環(huán)運行,以便等待客戶機(jī)的另一個請求。注意,該應(yīng)用程序決不會調(diào)用R e s e t E v e n t函數(shù),因為沒有必要。自動重置的事件在等待成功后會自動恢復(fù)未通知狀態(tài)。與此同時,客戶機(jī)線程發(fā)現(xiàn)g _ h e v t R e s u l t R e t u r n e d

47、事件已經(jīng)變?yōu)橐淹ㄖ獱顟B(tài)。它醒來,并將字符串從共享內(nèi)存緩沖區(qū)拷貝到用戶界面的R e s u l t域。也許這個應(yīng)用程序剩下的唯一的一個值得注意的特性是它如何關(guān)閉。若要關(guān)閉該應(yīng)用程序,只需要關(guān)閉它的對話框。這會導(dǎo)致調(diào)用的_ t Wi n M a i n中的D i a l o g B o x函數(shù)返回。這時,主線程將一個特殊字符串拷貝到共享緩沖區(qū),并喚醒服務(wù)器的線程,以便處理該特殊請求。主線程等待服務(wù)器線程確認(rèn)請求已經(jīng)收到,并等待服務(wù)器線程終止運行。當(dāng)服務(wù)器線程發(fā)現(xiàn)該特殊的客戶機(jī)請求字符串時,它就退出循環(huán),而該線程則終止運行。我選擇讓主線程等待服務(wù)器線程終止運行,方法是調(diào)用Wa i t F o r M

48、 u l t i p l e O b j e c t s函數(shù),這樣,就可以看到該函數(shù)是如何使用的。實際上,也可以調(diào)用Wa i t F o r S i n g l e O b j e c t函數(shù),傳遞服務(wù)器線程的句柄,一切將以完全相同的方式來運行。一旦主線程知道服務(wù)器線程已經(jīng)停止運行后,我將3次調(diào)用C l o s e H a n d l e函數(shù),以便正確地撤消應(yīng)用程序正在使用的所有內(nèi)核對象。當(dāng)然,系統(tǒng)能夠自動執(zhí)行這項操作,但是如果我自己進(jìn)行操作,我的感覺會更好些。我喜歡能夠隨時控制我的代碼。清單9-1 Handshake示例應(yīng)用程序/*Module: Handshake.cppNotices:

49、Copyright (c) 2000 Jeffrey Richter*/#include .CmnHdr.h /* See Appendix A. */#include #include #include / For beginthreadex#include Resource.h/ This event is signaled when the client has a request for the serverHANDLE g_hevtRequestSubmitted;/ This event is signaled when the server has a result for th

50、e clientHANDLE g_hevtResultReturned;/ The buffer shared between the client and server threadsTCHAR g_szSharedRequestAndResultBuffer1024;/ The special value sent from the client that causes the / server thread to terminate cleanly.TCHAR g_szServerShutdown = TEXT(Server Shutdown);/ This is the code ex

51、ecuted by the server threadDWORD WINAPI ServerThread(PVOID pvParam) / Assume that the server thread is to run forever BOOL fShutdown = FALSE; while (!fShutdown) / Wait for the client to submit a request WaitForSingleObject(g_hevtRequestSubmitted, INFINITE); / Check to see if the client wants the ser

52、ver to terminate fShutdown = (lstrcmpi(g_szSharedRequestAndResultBuffer, g_szServerShutdown) = 0); if (!fShutdown) / Process the clients request (reverse the string) _tcsrev(g_szSharedRequestAndResultBuffer); / Let the client process the requests result SetEvent(g_hevtResultReturned); / The client w

53、ants us to shutdown, exit return(0);/BOOL Dlg_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam) chSETDLGICONS(hwnd, IDI_HANDSHAKE); / Initialize the edit control with some test data request Edit_SetText(GetDlgItem(hwnd, IDC_REQUEST), TEXT(Some test data); return(TRUE);/void Dlg_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify) switch (id) case IDCANCEL: EndDialog(hwnd, id); break; case IDC_SUBMIT: / Submit a reques

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論