摘要:在Windows 95中所有的應(yīng)用程序?qū)嶋H上都以是線程的方式運(yùn)行的。在設(shè)計(jì)多線程應(yīng)用程序中有時(shí)必須在線程之間保持一定的同步關(guān)系,才能使用戶能夠?qū)Κ?dú)立運(yùn)行的線程進(jìn)行有效的控制。為此本文在簡(jiǎn)要介紹Windows 95中線程的概念及其創(chuàng)建方法后,提出了一種在多線程之間利用 event對(duì)象實(shí)現(xiàn)事件同步的控制方法。最后還介紹了在不同應(yīng)用程序之間進(jìn)行同步事件控制的方法,這種方法使得不同應(yīng)用程序進(jìn)行相互間的同步事件控制變得很簡(jiǎn)單。
關(guān)鍵詞:Windows95 線程
同步事件 event
對(duì)象 Win32
一, 引言
Windows 95是一個(gè)多任務(wù)、多線程的操作系統(tǒng),其中的每一個(gè)應(yīng)用程序都是一個(gè)進(jìn)程(process)。進(jìn)程可以創(chuàng)建多個(gè)并發(fā)的線程(thread),同時(shí)進(jìn)程也以主線程(primarythread)的形式被系統(tǒng)調(diào)度。所謂的線程是系統(tǒng)調(diào)度的一個(gè)基本單位, 在程序中線程是以函數(shù)的形式出現(xiàn)的,它的代碼是進(jìn)程代碼的一部分,并與進(jìn)程及其派生的其它線程共享進(jìn)程的全局變量和文件打開表等公用信息。主線程類似于UNIX系統(tǒng)中的父進(jìn)程,線程則類似于子進(jìn)程。主線程也是一個(gè)線程,稱作主線程僅僅是為了和它創(chuàng)建的線程區(qū)別開來。每個(gè)線程都相對(duì)于主線程而獨(dú)立運(yùn)行,為了使得線程能對(duì)用戶的控制作出響應(yīng),必須控制線程的運(yùn)行,比如用戶可暫停、終止一個(gè)線程的運(yùn)行或改變線程運(yùn)行的條件等。而且在用戶控制與線程運(yùn)行之間有時(shí)應(yīng)該有一定的同步控制關(guān)系,以保證用戶對(duì)線程的有效控制。線程可以根據(jù)不同的條件對(duì)用戶的控制作出不同的響應(yīng)。為了實(shí)現(xiàn)上述目的必須使用系統(tǒng)提供的同步對(duì)象(Synchronization Object),如event對(duì)象。 編寫多線程應(yīng)用程序必須使用Win32 API。
二, 線程的創(chuàng)建方法
調(diào)用Win32 API中的CreateThread函數(shù)創(chuàng)建線程。hThread=CreateThread(NULL,0,&TEventWindow::ThreadFunc,this,0,&hThreadId);第一個(gè)參數(shù)設(shè)定線程的安全屬性,因其僅用于Windows NT,故不設(shè)定。第二個(gè)參數(shù)為0指定線程使用缺省的堆棧大小。第三個(gè)參數(shù)指定線程函數(shù),線程即從該函數(shù)的入口處開始運(yùn)行,函數(shù)返回時(shí)就意味著線程終止運(yùn)行。第四個(gè)參數(shù)為線程函數(shù)的參數(shù),可以是指向任意數(shù)據(jù)類型的指針。第五個(gè)參數(shù)設(shè)定線程的生成標(biāo)志。hThreadId存放線程的標(biāo)識(shí)號(hào)。線程函數(shù)如下定義,上述的 this參數(shù)是指向線程所屬窗口的句柄指針,通過thrdWin參數(shù)傳送過來,利用這個(gè)指針再調(diào)用相應(yīng)的LoopFunc函數(shù),線程的具體事務(wù)都在這個(gè)函數(shù)中執(zhí)行。
DWORD _stdcall TEventWindow::ThreadFunc(void *thrdWin){
return STATIC_CAST(TEventWindow*,thrdWin)->LoopFunc( );
}
三, 線程的同步事件控制方法
Windows 95提供兩種基本類型的系統(tǒng)對(duì)象,一種是彼此互斥的對(duì)象,用來協(xié)調(diào)訪問數(shù)據(jù),如 mutex對(duì)象;一種是事件同步對(duì)象,用來發(fā)送命令或觸發(fā)事件,安排事件執(zhí)行的先后次序,如 event對(duì)象。系統(tǒng)對(duì)象在系統(tǒng)范圍內(nèi)有效,它們都具有自己的安全屬性、訪問權(quán)限和以下兩種狀態(tài)中的一種:Signaled和nonSignaled。對(duì)于event對(duì)象調(diào)用SetEvent函數(shù)可將其狀態(tài)設(shè)為Signaled,調(diào)用ResetEvent函數(shù)則可將其狀態(tài)設(shè)為nonSignaled。演示程序中的線程在一個(gè)大循環(huán)中不斷地將運(yùn)行結(jié)果顯示出來,當(dāng)用戶要關(guān)閉窗口時(shí)線程才終止運(yùn)行。不過必須在窗口關(guān)閉之前先終止線程的運(yùn)行,否則線程運(yùn)行的結(jié)果將會(huì)顯示在屏幕的其他地方,所以有必要在線程結(jié)束與關(guān)閉窗口這兩個(gè)事件之間建立起同步關(guān)系。為此在TEventWindow類的構(gòu)造函數(shù)中創(chuàng)建兩個(gè)event對(duì)象,用來實(shí)現(xiàn)事件同步。hCloseEvent=CreateEvent(0,FALSE,FALSE,0); hNoCloseEvent=CreateEvent(0,FALSE,FALSE,0);第二個(gè)參數(shù)為FALSE 表示創(chuàng)建的是一個(gè)自動(dòng)event對(duì)象,第三個(gè)參數(shù)為FALSE表示對(duì)象的初始狀態(tài)為nonSignaled,第四個(gè)參數(shù)為0表示該對(duì)象沒有名字。在TEventWindow類的構(gòu)造函數(shù)中還同樣創(chuàng)建hWatchEvent和hNtyEvent對(duì)象,初始狀態(tài)都為nonSignaled。用戶要關(guān)閉窗口時(shí),程序首先調(diào)用CanClose 函數(shù),在該函數(shù)中設(shè)置hCloseEvent對(duì)象的狀態(tài)為Signaled,利用這個(gè)方法來通知線程,要求線程終止運(yùn)行。然后主線程調(diào)用函數(shù)WaitForMultipleObjects(該函數(shù)以下簡(jiǎn)稱wait函數(shù) ),wait函數(shù)先判斷對(duì)象hThread和hNoCloseEvent中任意一個(gè)的狀態(tài)是否為Signaled, 如果都不是就堵塞主線程的運(yùn)行,直到上述條件滿足;如果有一個(gè)對(duì)象的狀態(tài)為Signaled,wait函數(shù)就返回,不再堵塞主線程。如果對(duì)象是自動(dòng)event對(duì)象,wait函數(shù)在返回之前還會(huì)將對(duì)象的狀態(tài)設(shè)為nonSignaled。wait函數(shù)中的參數(shù)FALSE表示不要求兩個(gè)對(duì)象的狀態(tài)同時(shí)為Signaled,參數(shù)-1表示要無限期地等待下去直到條件滿足,參數(shù)2表示SignalsC數(shù)組中有兩個(gè)對(duì)象。在Windows 95中線程也被看作是一種系統(tǒng)對(duì)象,同樣具有兩種狀態(tài)。線程運(yùn)行時(shí)其狀態(tài)為nonSignaled,如果線程終止運(yùn)行,則其狀態(tài)被系統(tǒng)自動(dòng)設(shè)為Signaled( 可以通過線程的句柄hThread得到線程狀態(tài)),此時(shí)wait函數(shù)返回0,表示第一個(gè)對(duì)象滿足條件,于是CanClose返回TRUE表示窗口可以關(guān)閉;如果線程不能滿足終止運(yùn)行的條件,就設(shè)置hNoCloseEvent 對(duì)象的狀態(tài)為Signaled,此時(shí)wait函數(shù)返回1,表示第二個(gè)對(duì)象滿足條件,于是CanClose返回FALSE表示窗口暫時(shí)還不能關(guān)閉。
BOOL TEventWindow::CanClose(){
HANDLE SignalsC[2]={hThread,hNoCloseEvent};
SetEvent(hCloseEvent);
if(WaitForMultipleObjects(2,SignalsC,FALSE,-1)==0) return TRUE;
else return FALSE;
}
另一個(gè)用戶控制的例子是,用戶使主線程暫停運(yùn)行直到線程滿足某種條件為止。比如用戶選擇“Watch”菜單后,主線程調(diào)用如下函數(shù)開始對(duì)線程的運(yùn)算數(shù)據(jù)進(jìn)行監(jiān)測(cè)。 首先設(shè)置hWatchEvent對(duì)象的狀態(tài)為Signaled,以此來通知線程, 主線程此時(shí)已進(jìn)入等待狀態(tài)并開始對(duì)數(shù)據(jù)進(jìn)行監(jiān)測(cè),然后主線程調(diào)用wait函數(shù)等待線程的回應(yīng)。線程在滿足某個(gè)條件后就設(shè)置hNtyEvent對(duì)象的狀態(tài)為Signaled,使主線程結(jié)束等待狀態(tài),繼續(xù)運(yùn)行。
void TEventWindow::CmWatch(){
SetEvent(hWatchEvent);
WaitForSingleObject(hNtyEvent,-1);
::MessageBox(GetFocus(),"線程已符合條件,主線程繼續(xù)運(yùn)行!","",MB_OK);
}
線程函數(shù)所調(diào)用的LoopFunc是一個(gè)大循環(huán),它不斷地判斷同步對(duì)象的狀態(tài),并根據(jù)這些對(duì)象的狀態(tài)執(zhí)行相應(yīng)的操作,這些對(duì)象在數(shù)組SignalsL中列出。在這個(gè)數(shù)組中各元素的排列順序是很重要的,前兩個(gè)對(duì)象分別對(duì)應(yīng)兩種不同的用戶控制事件,通過判斷對(duì)象的狀態(tài)可以知道發(fā)生的是哪一種用戶控制。只有當(dāng)前面兩個(gè)對(duì)象的狀態(tài)都不是Signaled時(shí)才會(huì)判斷第三個(gè)對(duì)象的狀態(tài),這樣一方面保證線程能檢測(cè)到所有的用戶控制事件,另一方面又保證了在不發(fā)生用戶控制事件時(shí)線程也能繼續(xù)運(yùn)行。為此特地在TEventWindow類的構(gòu)造函數(shù)中創(chuàng)建的對(duì)象hNoBlockEvent的狀態(tài)始終為Signaled。
hNoBlockEvent=CreateEvent(0,TRUE,TRUE,"MyEvent");
第二個(gè)參數(shù)為TRUE表示創(chuàng)建的是一個(gè)手工event對(duì)象, 其狀態(tài)是不會(huì)被wait函數(shù)所改變的,除非顯式地調(diào)用ResetEvent函數(shù)。第三個(gè)參數(shù)為TRUE表示對(duì)象初始狀態(tài)為Signaled,第四個(gè)參數(shù)定義了該對(duì)象的名字為“MyEvent”。LoopFunc函數(shù)調(diào)用wait函數(shù),如果檢測(cè)到hCloseEvent的狀態(tài)為Signaled, 此時(shí)wait函數(shù)返回0,線程知道用戶要關(guān)閉窗口了,就判斷線程是否可以終止,條件是iCount>100,如果滿足終止條件LoopFunc函數(shù)就返回,實(shí)際上就終止了線程的運(yùn)行;如果不滿足條件線程就設(shè)置 hNoCloseEvent對(duì)象的狀態(tài)為Signaled,讓主線程知道線程暫時(shí)還不能終止。由于hCloseEvent是自動(dòng)event對(duì)象,所以wait函數(shù)返回0時(shí)還會(huì)將對(duì)象hCloseEvent的狀態(tài)設(shè)置為nonSignaled,這樣在第二次循環(huán)時(shí),wait函數(shù)就不會(huì)判斷出hCloseEvent對(duì)象的狀態(tài)為Signaled,避免了線程錯(cuò)誤地再次去判斷是否會(huì)滿足終止條件。如果wait函數(shù)檢測(cè)到對(duì)象hWatchEvent的狀態(tài)為Signaled,此時(shí)wait函數(shù)返回1,線程知道主線程已進(jìn)入等待狀態(tài)并在對(duì)數(shù)據(jù)進(jìn)行監(jiān)測(cè),就設(shè)置變量bWatch的值為TRUE。如果前面的兩個(gè)事件都未發(fā)生,則前面兩個(gè)對(duì)象的狀態(tài)都為nonSignaled,于是wait函數(shù)就檢測(cè)第三個(gè)對(duì)象的狀態(tài), 由于第三個(gè)對(duì)象hNoBlockEvent 的狀態(tài)始終為Signaled,所以線程就無阻礙地繼續(xù)運(yùn)行下去,將變量iCount不斷加一,當(dāng)變量大于200時(shí),如果bWatch為TRUE,就設(shè)置hNtyEvent的狀態(tài)為
Signaled,從而使主線程停止等待,繼續(xù)運(yùn)行。
DWORD TEventWindow::LoopFunc(){
HANDLE SignalsL[3]={hCloseEvent,hWatchEvent,hNoBlockEvent};
static BOOL bWatch=false;int dwEvent;
while(1){
dwEvent=WaitForMultipleObjects(3,SignalsL,FALSE,-1);
switch(dwEvent){
case 0: if(iCount>100) return 0;
else SetEvent(hNoCloseEvent);
break;
case 1: bWatch=TRUE;break;
case 2: ++iCount;
if(bWatch && iCount>200) SetEvent(hNtyEvent);
break;
}
}
}
四, 進(jìn)程間的多線程同步事件控制方法
由于event對(duì)象是系統(tǒng)范圍內(nèi)有效的,所以另一個(gè)進(jìn)程(即一個(gè)應(yīng)用程序,本身也是一個(gè)線程)可調(diào)用OpenEvent函數(shù),通過對(duì)象的名字獲得對(duì)象的句柄, 但對(duì)象必須是已經(jīng)創(chuàng)建的,然后可將這個(gè)句柄用于ResetEvent、SetEvent和WaitForMultipleObjects等函數(shù)中。這樣可以實(shí)現(xiàn)一個(gè)進(jìn)程的線程控制另一進(jìn)程生成的線程的運(yùn)行。如下面的語(yǔ)句就是通過對(duì)象名字“MyEvent”獲得了上面進(jìn)程生成的hNoBlockEvent對(duì)象的句柄,再使用這個(gè)句柄將對(duì)象狀態(tài)設(shè)為nonSignaled。在上述的 LoopFunc函數(shù)中由于該對(duì)象的狀態(tài)已經(jīng)改變,使得上面的線程暫停運(yùn)行。
HANDLE hEvent=OpenEvent(EVENT_ALL_ACCESS,true,"MyEvent");
ResetEvent(hEvent);
OpenEvent函數(shù)的第一個(gè)參數(shù)表示函數(shù)的調(diào)用線程對(duì)event對(duì)象的訪問權(quán)限,比如讓線程擁有對(duì)象所有的訪問權(quán)限,就選參數(shù)EVENT_ALL_ACCESS,這樣線程就能用ResetEvent函數(shù)改變對(duì)象的狀態(tài);參數(shù)true表示由這個(gè)進(jìn)程派生的子進(jìn)程可以繼承該句柄;最后一個(gè)參數(shù)指出了event對(duì)象的名字。用下面的語(yǔ)句設(shè)置對(duì)象hNoBlockEvent的狀態(tài)為Signaled,就可以使線程繼續(xù)運(yùn)行,如SetEvent(hEvent)。
進(jìn)程不再使用該句柄時(shí)盡可以用CloseHandle函數(shù)關(guān)閉對(duì)象句柄,但對(duì)于同一個(gè)event對(duì)象而言,因?yàn)樗赡苓€在別的線程中被使用,所以只有在它的所有被引用的句柄都關(guān)閉后對(duì)象才會(huì)被系統(tǒng)釋放,文中提到的所有 event對(duì)象在主線程和線程之間以及在不同的進(jìn)程之間所起的控制作用如圖1所示:
① ┌───────┐ ①:關(guān)閉窗口
┌──→─┤ hCloseEvent ├───┐ ②:對(duì)上面事件的反應(yīng)
│ └───────┘ │ |
│ ┌───────┐ ↓ | 暫停/恢復(fù)線程的運(yùn)行
│ │ hThread 或 │②┌─┴─┐ ┌───────┐ ┌───┐
┌─┴─┐ ┌┤hNoCloseEvent ├←┤ 線程 ├←┤hNoBlockEvent ├←┤進(jìn)程 2│
│主線程├←┘└───────┘ └┬─┬┘ └───────┘ └───┘
│/進(jìn)程1├→┐┌───────┐ ↑ │ |不同進(jìn)程之間
└─┬─┘⑴└┤ hWatchEvent ├──┘ │ |的地址界限
↑ └───────┘ │
│ ┌───────┐ │ ⑴:監(jiān)測(cè)數(shù)據(jù)
└────┤ hNtyEvent ├←───┘ ⑵:線程滿足監(jiān)測(cè)條件
└───────┘⑵
圖1 event對(duì)象在多線程間同步事件控制中的作用
五, 結(jié)束語(yǔ)
多線程編程技術(shù)在多媒體、網(wǎng)絡(luò)通訊、數(shù)學(xué)計(jì)算和實(shí)時(shí)控制方面有著很廣闊的應(yīng)用前景。當(dāng)然在實(shí)際編程中情況往往是很復(fù)雜的,這時(shí)應(yīng)注意的是如何將任務(wù)準(zhǔn)確地劃分成可并發(fā)的線程以及象文中提到的SignalsL數(shù)組中元素的排列順序等問題。本文所講內(nèi)容對(duì)于在Windows NT或在某些支持多線程的UNIX系統(tǒng)中設(shè)計(jì)多線程應(yīng)用程序也是有所幫助的。