Windows核心编程 第九章 线程与内核对象的同步(上)
第9章?線程與內(nèi)核對象的同步
? ? 上一章介紹了如何使用允許線程保留在用戶方式中的機(jī)制來實(shí)現(xiàn)線程同步的方法。用戶方式同步的優(yōu)點(diǎn)是它的同步速度非??臁H绻麖?qiáng)調(diào)線程的運(yùn)行速度,那么首先應(yīng)該確定用戶方式的線程同步機(jī)制是否適合需要。
? ? 雖然用戶方式的線程同步機(jī)制具有速度快的優(yōu)點(diǎn),但是它也有其局限性。對于許多應(yīng)用程序來說,這種機(jī)制是不適用的。例如,互鎖函數(shù)家族只能在單值上運(yùn)行,根本無法使線程進(jìn)入等待狀態(tài)。可以使用關(guān)鍵代碼段使線程進(jìn)入等待狀態(tài),但是只能用這些代碼段對單個(gè)進(jìn)程中的線程實(shí)施同步。還有,使用關(guān)鍵代碼段時(shí),很容易陷入死鎖狀態(tài),因?yàn)樵诘却M(jìn)入關(guān)鍵代碼段時(shí)無法設(shè)定超時(shí)值。
? ? 本章將要介紹如何使用內(nèi)核對象來實(shí)現(xiàn)線程的同步。你將會(huì)看到,內(nèi)核對象機(jī)制的適應(yīng)性遠(yuǎn)遠(yuǎn)優(yōu)于用戶方式機(jī)制。實(shí)際上,內(nèi)核對象機(jī)制的唯一不足之處是它的速度比較慢。當(dāng)調(diào)用本章中提到的任何新函數(shù)時(shí),調(diào)用線程必須從用戶方式轉(zhuǎn)為內(nèi)核方式。這個(gè)轉(zhuǎn)換需要很大的代價(jià):往返一次需要占用 x 8 6平臺(tái)上的大約1 0 0 0個(gè)C P U周期,當(dāng)然,這還不包括執(zhí)行內(nèi)核方式代碼,即實(shí)現(xiàn)線程調(diào)用的函數(shù)的代碼所需的時(shí)間。
? ? 本書介紹了若干種內(nèi)核對象,包括進(jìn)程,線程和作業(yè)。可以將所有這些內(nèi)核對象用于同步目的。對于線程同步來說,這些內(nèi)核對象中的每種對象都可以說是處于已通知或未通知的狀態(tài)之中。這種狀態(tài)的切換是由M i c r o s o f t為每個(gè)對象建立的一套規(guī)則來決定的。例如,進(jìn)程內(nèi)核對象總是在未通知狀態(tài)中創(chuàng)建的。當(dāng)進(jìn)程終止運(yùn)行時(shí),操作系統(tǒng)自動(dòng)使該進(jìn)程的內(nèi)核對象處于已通知狀態(tài)。一旦進(jìn)程內(nèi)核對象得到通知,它將永遠(yuǎn)保持這種狀態(tài),它的狀態(tài)永遠(yuǎn)不會(huì)改為未通知狀態(tài)。
? ? 當(dāng)進(jìn)程正在運(yùn)行的時(shí)候,進(jìn)程內(nèi)核對象處于未通知狀態(tài),當(dāng)進(jìn)程終止運(yùn)行的時(shí)候,它就變?yōu)橐淹ㄖ獱顟B(tài)。進(jìn)程內(nèi)核對象中是個(gè)布爾值,當(dāng)對象創(chuàng)建時(shí),該值被初始化為 FA L S E(未通知狀態(tài)) 。當(dāng)進(jìn)程終止運(yùn)行時(shí),操作系統(tǒng)自動(dòng)將對應(yīng)的對象布爾值改為 T R U E,表示該對象已經(jīng)得到通知。
? ? 如果編寫的代碼是用于檢查進(jìn)程是否仍在運(yùn)行,那么只需要調(diào)用一個(gè)函數(shù),讓操作系統(tǒng)去
? ? 檢查進(jìn)程對象的布爾值,這非常簡單。你也可能想要告訴系統(tǒng)使線程進(jìn)入等待狀態(tài),然后當(dāng)布爾值從FA L S E改為T R U E時(shí)自動(dòng)喚醒該線程。這樣,你可以編寫一個(gè)代碼,在這個(gè)代碼中,需要等待子進(jìn)程終止運(yùn)行的父進(jìn)程中的線程只需要使自己進(jìn)入睡眠狀態(tài),直到標(biāo)識(shí)子進(jìn)程的內(nèi)核對象變?yōu)橐淹ㄖ獱顟B(tài)即可。你將會(huì)看到, 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ī)則。實(shí)際上,線程內(nèi)核對象也遵循同樣的規(guī)則。即線程內(nèi)核對象總是在未通知狀態(tài)中創(chuàng)建。當(dāng)線程終止運(yùn)行時(shí),操作系統(tǒng)會(huì)自動(dòng)將線程對象的狀態(tài)改為已通知狀態(tài)。因此,可以將相同的方法用于應(yīng)用程序,以確定線程是否不再運(yùn)行。與進(jìn)程內(nèi)核對象一樣,線程內(nèi)核對象也可以處于已通知狀態(tài)或未通知狀態(tài)。
????下面的內(nèi)核對象可以處于已通知狀態(tài)或未通知狀態(tài):
????■ 進(jìn)程 ■ 文件修改通知
????■ 線程 ■ 事件
????■ 作業(yè) ■ 可等待定時(shí)器
????■ 文件 ■ 信標(biāo)
????■ 控制臺(tái)輸入 ■ 互斥對象
? ? 線程可以使自己進(jìn)入等待狀態(tài),直到一個(gè)對象變?yōu)橐淹ㄖ獱顟B(tài)。注意,用于控制每個(gè)對象的已通知/未通知狀態(tài)的規(guī)則要根據(jù)對象的類型而定。前面已經(jīng)提到進(jìn)程和線程對象的規(guī)則及作業(yè)的規(guī)則。
9.1 等待函數(shù)
? ? 等待函數(shù)可使線程自愿進(jìn)入等待狀態(tài),直到一個(gè)特定的內(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 t :
DWORD WaitForSingObject(
HANDLE hObject,
DWORD dwMilliseconds);
? ? 當(dāng)線程調(diào)用該函數(shù)時(shí),第一個(gè)參數(shù) h O b j e c t標(biāo)識(shí)一個(gè)能夠支持被通知 /未通知的內(nèi)核對象(前面列出的任何一種對象都適用) 。第二個(gè)參數(shù)d w M i l l i s e c o n d s允許該線程指明,為了等待該對象變?yōu)橐淹ㄖ獱顟B(tài),它將等待多長時(shí)間。
? ? 調(diào)用下面這個(gè)函數(shù)將告訴系統(tǒng),調(diào)用函數(shù)準(zhǔn)備等待到h P r o c e s s句柄標(biāo)識(shí)的進(jìn)程終止運(yùn)行為止:
? ? WaitForSingleObject(hProcess ,INFINITE);
? ? 第二個(gè)參數(shù)告訴系統(tǒng),調(diào)用線程愿意永遠(yuǎn)等待下去(無限時(shí)間量) ,直到該進(jìn)程終止運(yùn)行。
? ? 通常情況下,I N F I N I T E是作為第二個(gè)參數(shù)傳遞給Wa i t F o r S i n g l e O b j e c t的,不過也可以傳遞任何一個(gè)值(以毫秒計(jì)算) 。順便說一下,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有些危險(xiǎn)。如果對象永遠(yuǎn)不變?yōu)橐淹ㄖ獱顟B(tài),那么調(diào)用線程永遠(yuǎn)不會(huì)被喚醒,它將永遠(yuǎn)處于死鎖狀態(tài),不過,它不會(huì)浪費(fèi)寶貴的C P U時(shí)間。
? ? 下面是如何用一個(gè)超時(shí)值而不是I N F I N I T E來調(diào)用Wa i t F o r S i n g l e O b j e c t的例子:
?
? ? 上面這個(gè)代碼告訴系統(tǒng),在特定的進(jìn)程終止運(yùn)行之前,或者在 5 0 0 0 m s時(shí)間結(jié)束之前,調(diào)用線程不應(yīng)該變?yōu)榭烧{(diào)度狀態(tài)。因此,如果進(jìn)程終止運(yùn)行,那么這個(gè)函數(shù)調(diào)用將在不到5 0 0 0 m s的時(shí)間內(nèi)返回,如果進(jìn)程尚未終止運(yùn)行,那么它在大約 5 0 0 0 m s時(shí)間內(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 T _ O B J E C T _ 0。如果設(shè)置的超時(shí)已經(jīng)到期,則返回值是WA I T _ T I M E O U T。如果將一個(gè)錯(cuò)誤的值(如一個(gè)無效句柄)傳遞給 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) 。
? ? 下面這個(gè)函數(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)用線程同時(shí)查看若干個(gè)內(nèi)核對象的已通知狀態(tài):
DWORD WaitForMultipleObjects(
????DWORD dwCount,
????CONST HANDLE*phObjects,
????BOOL fWaitAll,
????DWORD dwMilliseconds
);
? ? ?d w C o u n t參數(shù)用于指明想要讓函數(shù)查看的內(nèi)核對象的數(shù)量。這個(gè)值必須在 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ù)組的指針。
可以以兩種不同的方式來使用Wa i t F o r M u l t i p l e O b j e c t s函數(shù)。一種方式是讓線程進(jìn)入等待狀態(tài),直到指定內(nèi)核對象中的任何一個(gè)變?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)用線程運(yùn)行。
? ? 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中的作用完全相同。如果在等待的時(shí)候規(guī)定的時(shí)間到了,那么該函數(shù)無論如何都會(huì)返回。同樣,通常為該參數(shù)傳遞 I N F I N I T E,但是在編寫代碼時(shí)應(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)用線程,為什么它會(huì)被重新調(diào)度??赡艿姆祷刂凳?/span>WA I T _ FA I L E D和WA I T _ T I M E O U T,這兩個(gè)值的作用是很清楚的。如果為 f Wa i tAl l參數(shù)傳遞T R U E,同時(shí)所有對象均變?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,那么一旦任何一個(gè)對象變?yōu)橐淹ㄖ獱顟B(tài),該函數(shù)便返回。在這種情況下,你可能想要知道哪個(gè)對象變?yōu)橐淹ㄖ獱顟B(tài)。返回值是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)之間的一個(gè)值。換句話說,如果返回值不是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ù)字是作為第二個(gè)參數(shù)傳遞給Wa i t F o r M u l t i p l e O b j e c t s的句柄數(shù)組中的索引。該索引說明哪個(gè)對象變?yōu)橐淹ㄖ獱顟B(tài)。下面是說明這一情況的一些示例代碼:
?
? ? 如果為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)行掃描,同時(shí)已通知的第一個(gè)對象終止等待狀態(tài)。這可能產(chǎn)生一些你不希望有的結(jié)果。例如,通過將3個(gè)進(jìn)程句柄傳遞給該函數(shù),你的線程就會(huì)等待 3個(gè)子進(jìn)程終止運(yùn)行。如果數(shù)組中索引為0的進(jìn)程終止運(yùn)行,Wa i t F o r M u l t i p l e O b j e c t s就會(huì)返回。這時(shí)該線程就可以做它需要的任事情,然后循環(huán)反復(fù),等待另一個(gè)進(jìn)程終止運(yùn)行。如果該線程傳遞相同的 3個(gè)句柄,該函數(shù)立即再次返回WA I T _ O B J E C T _ 0。除非刪除已經(jīng)收到通知的句柄,否則代碼就無法正確地運(yùn)行。
9.2 成功等待的副作用
? ? 對于有些內(nèi)核對象來說,成功地調(diào)用 Wa i t F o r S i n g l 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,實(shí)際上會(huì)改變對象的狀態(tài)。成功地調(diào)用是指函數(shù)發(fā)現(xiàn)對象已經(jīng)得到通知并且返回一個(gè)相對于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)一個(gè)對象的狀態(tài)改變時(shí),我稱之為成功等待的副作用。例如,有一個(gè)線程正在等待自動(dòng)清除事件對象(本章后面將要介紹) 。當(dāng)事件對象變?yōu)橐淹ㄖ獱顟B(tài)時(shí),函數(shù)就會(huì)發(fā)現(xiàn)這個(gè)情況,并將WA I T _ O B J E C T _ 0返回給調(diào)用線程。但是就在函數(shù)返回之前,該事件將被置為未通知狀態(tài),這就是成功等待的副作用。
這個(gè)副作用將用于自動(dòng)清除內(nèi)核對象,因?yàn)樗?/span> M i c r o s o f t為這種類型的對象定義的規(guī)則之一。其他對象擁有不同的副作用,而有些對象則根本沒有任何副作用。進(jìn)程和線程內(nèi)核對象就根本沒有任何副作用,也就是說,在這些對象之一上進(jìn)行等待決不會(huì)改變對象的狀態(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ù)如此有用呢,因?yàn)樗軌蛞栽硬僮鞣绞絹韴?zhí)行它的所有操作。當(dāng)一個(gè)線程調(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í),該函數(shù)能夠測試所有對象的通知狀態(tài),并且能夠?qū)⑺斜匾母弊饔米鳛橐豁?xiàng)操作來執(zhí)行。
讓我們觀察一個(gè)例子。兩個(gè)線程以完全相同的方式來調(diào)用 Wa i t F o r M u l t i p l e O b j e c t s:
?
? ? 當(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)用時(shí),兩個(gè)事件都處于未通知狀態(tài),這就迫使兩個(gè)線程都進(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)。兩個(gè)線程都發(fā)現(xiàn),該事件已經(jīng)變?yōu)橐淹ㄖ獱顟B(tài),但是它們都無法被喚醒,因?yàn)?h A u t o R e s e t E v e n t 2仍然處于未通知狀態(tài)。由于兩個(gè)線程都沒有等待成功,因此沒有對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 e n t 2變?yōu)橐淹ㄖ獱顟B(tài)。這時(shí),兩個(gè)線程中的一個(gè)發(fā)現(xiàn),兩個(gè)對象都變?yōu)橐淹ㄖ獱顟B(tài)。等待取得了成功,兩個(gè)事件對象均被置為未通知狀態(tài),該線程變?yōu)榭烧{(diào)度的線程。但是另一個(gè)線程的情況如何呢?它將繼續(xù)等待,直到它發(fā)現(xiàn)兩個(gè)事件對象都處于已通知狀態(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)。
? ? 前面講過,有一個(gè)重要問題必須注意,即Wa i t F o r M u l t i p l e O b j e c t s是以原子操作方式運(yùn)行的。當(dāng)它檢查內(nèi)核對象的狀態(tài)時(shí),其他任何線程都無法背著對象改變它的狀態(tài)。這可以防止出現(xiàn)死鎖情況。試想,如果一個(gè)線程看到h A u t o R e s e t E v e n t 1已經(jīng)得到通知并將事件重置為未通知狀態(tài),然后,另一個(gè)線程發(fā)現(xiàn)h A u t o R e s e t E v e n t 2已經(jīng)得到通知并將該事件重置為未通知狀態(tài),那么這兩個(gè)線程均將被凍結(jié):一個(gè)線程將等待另一個(gè)線程已經(jīng)得到的對象,另一個(gè)線程將等待該線程已經(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)不會(huì)發(fā)生。
? ? 這會(huì)產(chǎn)生一個(gè)非常有趣的問題,即如果多個(gè)線程等待單個(gè)內(nèi)核對象,那么當(dāng)該對象變成已通知狀態(tài)時(shí),系統(tǒng)究竟決定喚醒哪個(gè)線程呢? M i c r o s o f t對這個(gè)問題的正式回答是: “算法是公平的。 ”M i c r o s o f t不想使用系統(tǒng)使用的內(nèi)部算法。它只是說該算法是公平的,這意味著如果多個(gè)線程正在等待,那么每當(dāng)對象變?yōu)橐淹ㄖ獱顟B(tài)時(shí),每個(gè)線程都應(yīng)該得到它自己的被喚醒的機(jī)會(huì)。
? ? 這意味著線程的優(yōu)先級(jí)不起任何作用,即高優(yōu)先級(jí)線程不一定得到該對象。這還意味著等待時(shí)間最長的線程不一定得到該對象。同時(shí)得到對象的線程有可能反復(fù)循環(huán),并且再次得到該對象。但是,這對于其他線程來說是不公平的,因此該算法將設(shè)法防止這種情況的出現(xiàn)。但是這不一定做得到。
? ? 在實(shí)際操作中,M i c r o s o f t使用的算法是常用的“先進(jìn)先出”的方案。等待了最長時(shí)間的線程將得到該對象。但是系統(tǒng)中將會(huì)執(zhí)行一些操作,以便改變這個(gè)行為特性,使它不太容易預(yù)測。這就是為什么M i c r o s o f t沒有明確說明該算法如何起作用的原因。操作之一是讓線程暫停運(yùn)行。如果一個(gè)線程等待一個(gè)對象,然后該線程暫停運(yùn)行,那么系統(tǒng)就會(huì)忘記該線程正在等待該對象。這是一個(gè)特性,因?yàn)闆]有理由為一個(gè)暫停運(yùn)行的線程進(jìn)行調(diào)度。當(dāng)后來該線程恢復(fù)運(yùn)行時(shí),系統(tǒng)將認(rèn)為該線程剛剛開始等待該對象。
? ? 當(dāng)調(diào)試一個(gè)進(jìn)程時(shí),只要到達(dá)一個(gè)斷點(diǎn),該進(jìn)程中的所有線程均暫停運(yùn)行。因此,調(diào)試一個(gè)進(jìn)程會(huì)使“先進(jìn)先出”的算法很難預(yù)測其結(jié)果,因?yàn)榫€程常常暫停運(yùn)行,然后再恢復(fù)運(yùn)行。
9.3 事件內(nèi)核對象
? ??在所有的內(nèi)核對象中,事件內(nèi)核對象是個(gè)最基本的對象。它們包含一個(gè)使用計(jì)數(shù)(與所有內(nèi)核對象一樣) ,一個(gè)用于指明該事件是個(gè)自動(dòng)重置的事件還是一個(gè)人工重置的事件的布爾值,另一個(gè)用于指明該事件處于已通知狀態(tài)還是未通知狀態(tài)的布爾值。
? ? 事件能夠通知一個(gè)操作已經(jīng)完成。有兩種不同類型的事件對象。一種是人工重置的事件,另一種是自動(dòng)重置的事件。當(dāng)人工重置的事件得到通知時(shí),等待該事件的所有線程均變?yōu)榭烧{(diào)度線程。當(dāng)一個(gè)自動(dòng)重置的事件得到通知時(shí),等待該事件的線程中只有一個(gè)線程變?yōu)榭烧{(diào)度線程。
? ? 當(dāng)一個(gè)線程執(zhí)行初始化操作,然后通知另一個(gè)線程執(zhí)行剩余的操作時(shí),事件使用得最多。事件初始化為未通知狀態(tài),然后,當(dāng)該線程完成它的初始化操作后,它就將事件設(shè)置為已通知狀態(tài)。這時(shí),一直在等待該事件的另一個(gè)線程發(fā)現(xiàn)該事件已經(jīng)得到通知,因此它就變成可調(diào)度線程。這第二個(gè)線程知道第一個(gè)線程已經(jīng)完成了它的操作。
? ? 下面是C r e a t e E v e n t函數(shù),用于創(chuàng)建事件內(nèi)核對象:
HANDLE CrreateEvent(
??PSECURITY_ATTRIBUTES psa,
??BOOL fManualReset,
??BOOL fInitialState,
??PCTSTR pszName
);
? ? 第3章已經(jīng)介紹了內(nèi)核對象的操作技巧,比如,如何設(shè)置它們的安全性,如何進(jìn)行使用計(jì)數(shù),如何繼承它們的句柄,如何按名字共享對象等。由于現(xiàn)在你對所有這些對象都已經(jīng)熟悉了,所以不再介紹該函數(shù)的第一個(gè)和最后一個(gè)參數(shù)。
? ? F M a n n u a l R e s e t參數(shù)是個(gè)布爾值,它能夠告訴系統(tǒng)是創(chuàng)建一個(gè)人工重置的事件( T R U E)還是創(chuàng)建一個(gè)自動(dò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 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è)定一個(gè)與調(diào)用C r e a t e E v e n t時(shí)設(shè)定的名字相匹配的名字:
HANDLE OpenEvent(
????DWORD fdwAccess,
????BOOL fInherit,
????PCTSTR pszName
);
? ? 與所有情況中一樣,當(dāng)不再需要事件內(nèi)核對象時(shí),應(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時(shí),可以將事件改為已通知狀態(tài):
? ? BOOL SetEvent(HANDLE hEvent);
? ? 當(dāng)調(diào)用R e s e t E v e n t函數(shù)時(shí),可以將該事件改為未通知狀態(tài):
? ? BOOL ResetEvent(HANDLE hEvent);
? ? M i c r o s o f t為自動(dòng)重置的事件定義了應(yīng)該成功等待的副作用規(guī)則,即當(dāng)線程成功地等待到該對象時(shí),自動(dòng)重置的事件就會(huì)自動(dòng)重置到未通知狀態(tài)。這就是自動(dòng)重置的事件如何獲得它們的名字的方法。通常沒有必要為自動(dòng)重置的事件調(diào)用 R e s e t E v e n t函數(shù),因?yàn)橄到y(tǒng)會(huì)自動(dòng)對事件進(jìn)行重置。但是,M i c r o s o f t沒有為人工重置的事件定義成功等待的副作用。
? ? 讓我們觀察一個(gè)簡單的例子,以便說明如何使用事件內(nèi)核對象對線程進(jìn)行同步。下面就是這個(gè)代碼:
?
????當(dāng)這個(gè)進(jìn)程啟動(dòng)時(shí),它創(chuàng)建一個(gè)人工重置的未通知狀態(tài)的事件,并且將句柄保存在一個(gè)全局變量中。這使得該進(jìn)程中的其他線程能夠非常容易地訪問同一個(gè)事件對象。現(xiàn)在 3個(gè)線程已經(jīng)產(chǎn)生。這些線程要等待文件的內(nèi)容讀入內(nèi)存,然后每個(gè)線程都要訪問它的數(shù)據(jù)。一個(gè)線程進(jìn)行單詞計(jì)數(shù),另一個(gè)線程運(yùn)行拼寫檢查器,第三個(gè)線程運(yùn)行語法檢查器。這 3個(gè)線程函數(shù)的代碼的開始部分都相同,每個(gè)函數(shù)都調(diào)用 Wa i t F o r S i n g l e O b j e c t,這將使線程暫停運(yùn)行,直到文件的內(nèi)容由主線程讀入內(nèi)存為止。
? ? 一旦主線程將數(shù)據(jù)準(zhǔn)備好,它就調(diào)用 S e t E v e n t,給事件發(fā)出通知信號(hào)。這時(shí),系統(tǒng)就使所有這3個(gè)輔助線程進(jìn)入可調(diào)度狀態(tài),它們都獲得了C P U時(shí)間,并且可以訪問內(nèi)存塊。注意,這3個(gè)線程都以只讀方式訪問內(nèi)存。這就是所有 3個(gè)線程能夠同時(shí)運(yùn)行的唯一原因。還要注意,如何計(jì)算機(jī)上配有多個(gè)C P U,那么所有3個(gè)線程都能夠真正地同時(shí)運(yùn)行,從而可以在很短的時(shí)間內(nèi)完成大量的操作。
? ? 如果你使用自動(dòng)重置的事件而不是人工重置的事件,那么應(yīng)用程序的行為特性就有很大的差別。當(dāng)主線程調(diào)用S e t E v e n t之后,系統(tǒng)只允許一個(gè)輔助線程變成可調(diào)度狀態(tài)。同樣,也無法保證系統(tǒng)將使哪個(gè)線程變?yōu)榭烧{(diào)度狀態(tài)。其余兩個(gè)輔助線程將繼續(xù)等待。
? ? 已經(jīng)變?yōu)榭烧{(diào)度狀態(tài)的線程擁有對內(nèi)存塊的獨(dú)占訪問權(quán)。讓我們重新編寫線程的函數(shù),使得每個(gè)函數(shù)在返回前調(diào)用S e t E v e n t函數(shù)(就像Wi n M a i n函數(shù)所做的那樣) 。這些線程函數(shù)現(xiàn)在變成下面的形式:
?
? ? 當(dāng)線程完成它對數(shù)據(jù)的專門傳遞時(shí),它就調(diào)用 S e t E v e n t函數(shù),該函數(shù)允許系統(tǒng)使得兩個(gè)正在等待的線程中的一個(gè)成為可調(diào)度線程。同樣,我們不知道系統(tǒng)將選擇哪個(gè)線程作為可調(diào)度線程,但是該線程將進(jìn)行它自己的對內(nèi)存塊的專門傳遞。當(dāng)該線程完成操作時(shí),它也將調(diào)用S e t E v e n t函數(shù),使第三個(gè)即最后一個(gè)線程進(jìn)行它自己的對內(nèi)存塊的傳遞。注意,當(dāng)使用自動(dòng)重置事件時(shí),如果每個(gè)輔助線程均以讀 /寫方式訪問內(nèi)存塊,那么就不會(huì)產(chǎn)生任何問題,這些線程將不再被要求將數(shù)據(jù)視為只讀數(shù)據(jù)。這個(gè)例子清楚地展示出使用人工重置事件與自動(dòng)重置事之間的差別。
? ? 為了完整起見,下面再介紹一個(gè)可以用于事件的函數(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ù)一樣。如果在人工重置的事件上調(diào)用P u l s e E v e n t函數(shù),那么在發(fā)出該事件時(shí),等待該事件的任何一個(gè)線程或所有線程將變?yōu)榭烧{(diào)度線程。如果在自動(dòng)重置事件上調(diào)用P u l s e E v e n t函數(shù),那么只有一個(gè)等待該事件的線程變?yōu)榭烧{(diào)度線程。如果在發(fā)出事件時(shí)沒有任何線程在等待該事件,那么將不起任何作用。
? ? P u l s e E v e n t函數(shù)并不非常有用。實(shí)際上我在自己的應(yīng)用程序中從未使用它,因?yàn)楦静恢朗裁淳€程將會(huì)看到事件的發(fā)出并變成可調(diào)度線程。由于在調(diào)用 P u l s e E v e n t時(shí)無法知道任何線程的狀態(tài),因此該函數(shù)并不那么有用。我相信在有些情況下,雖然 P u l s 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ù)的介紹。
總結(jié)
以上是生活随笔為你收集整理的Windows核心编程 第九章 线程与内核对象的同步(上)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Windows核心编程 第八章 用户方式
- 下一篇: Windows核心编程 第九章 线程与内