上次跟大家分享了線程的標(biāo)準(zhǔn)代碼,其實在線程的使用中最重要的是線程的同步問題,如果你在使用線程后,發(fā)現(xiàn)你的界面經(jīng)常被卡死,或者無法顯示出來,顯示混亂,你的使用的變量值老是不按預(yù)想的變化,結(jié)果往往出乎意料,那么你很有可能是忽略了線程同步的問題。 當(dāng)有多個線程的時候,經(jīng)常需要去同步這些線程以訪問同一個數(shù)據(jù)或資源。例如,假設(shè)有一個程序,其中一個線程用于把文件讀到內(nèi)存,而另一個線程用于統(tǒng)計文件中的字符數(shù)。當(dāng)然,在把整個文件調(diào)入內(nèi)存之前,統(tǒng)計它的計數(shù)是沒有意義的。但是,由于每個操作都有自己的 線程,操作系統(tǒng)會把兩個線程當(dāng)作是互不相干的任務(wù)分別執(zhí)行,這樣就可能在沒有把整個文 件裝入內(nèi)存時統(tǒng)計字?jǐn)?shù)。為解決此問題,你必須使兩個線程同步工作。存在一些線程同步地 址的問題,Windows 提供了許多線程同步的方式。在本節(jié)您將看到使用臨界區(qū)、互斥、信 號量、事件、全局原子和Synchronize 函數(shù)來解決線程同步的問題。 下面的同步技術(shù)一般均有兩種使用方式,一種是直接使用Windows API 函數(shù),一種是使用 由Delphi 對API 函數(shù)進(jìn)行封裝的類。 以下函數(shù)以Delphi 2009 中的函數(shù)格式為準(zhǔn)。 1. Critical Sections 臨界區(qū) 臨界區(qū)是一種最直接的線程同步方式。所謂臨界區(qū),就是一次只能由一個線程來執(zhí)行的一段 代碼。例如把初始化數(shù)組的代碼放在臨界區(qū)內(nèi),另一個線程在第一個線程處理完之前是不會 被執(zhí)行的。臨界區(qū)非常適合于序列化對一個進(jìn)程中的數(shù)據(jù)的訪問,因為它們的速度很快。 (1). 使用EnterCriticalSection( ) 和LeaveCriticalSection( ) API 函數(shù) 在使用臨界區(qū)之前, 必須定義一個TRTLCriticalSection 類型的記錄變量并使用 InitializeCriticalSection( ) 過程來初始化臨界區(qū)。該過程多半在窗體創(chuàng)建時或在程序初始化時 執(zhí)行。 其聲明如下: procedure InitializeCriticalSection(var lpCriticalSection : TRTLCriticalSection);stdcall; lpCriticalSection 參數(shù)是一個TRTLCriticalSection 類型的記錄, 并且是變參。至于 TRTLCriticalSection 是如何定義的,這并不重要,因為很少需要查看這個記錄中的具體內(nèi)容。 只需要在lpCriticalSection 中傳遞未初始化的記錄, InitializeCriticalSection( ) 過程就會填 充這個記錄。 注意:Microsoft 故意隱瞞了TRTLCriticalSection 的細(xì)節(jié)。因為,其內(nèi)容在不同的硬件平臺 上是不同的。在基于Intel 的平臺上,TRTLCriticalSection 包含一個計數(shù)器、一個指示當(dāng)前 線程句柄的域和一個系統(tǒng)事件的句柄。在Alpha 平臺上,計數(shù)器被替換為一種Alpha-CPU數(shù)據(jù)結(jié)構(gòu),稱為spinlock 。 在記錄被填充后,我們就可以開始創(chuàng)建臨界區(qū)了。這時我們需要用EnterCriticalSection( ) 和 LeaveCriticalSection( ) 來封裝代碼塊,這兩個函數(shù)分別代表進(jìn)入和離開臨界區(qū),將要同步的 代碼塊放在這兩個函數(shù)中間。在第一個線程調(diào)用了EnterCriticalSection( ) 之后,所有別的 線程就不能再進(jìn)入代碼塊并掛起等待第一個線程離開臨界區(qū)。下一個線程要等第一個線程調(diào) 用LeaveCriticalSection( ) 后才能被喚醒。這兩個過程的聲明如下: procedure EnterCriticalSection(var lpCriticalSection : TRTLCriticalSection);stdcall; //進(jìn)入臨界區(qū) procedure LeaveCriticalSection(var lpCriticalSection : TRTLCriticalSection);stdcall; //離開臨界 區(qū) 正如你所想的,參數(shù)lpCriticalSection 就是由InitializeCriticalSection( ) 填充的記錄。 如果在某個子線程執(zhí)行EnterCriticalSection( ) 前,已經(jīng)有另一個線程進(jìn)入臨界區(qū)且還未離 開臨界區(qū),則該子線程將掛起并無限期等待另一個線程離開臨界區(qū),要想不掛起且0 時間 等待,必須使用TryEnterCriticalSection( ) 。該過程聲明如下: function TryEnterCriticalSection(var lpCriticalSection: TRTLCriticalSection): BOOL; stdcall; TryEnterCriticalSection( ) 不同于EnterCriticalSection( ) 的聲明在于多出一個布爾型的返回 值,如果返回True 代表成功進(jìn)入臨界區(qū),如果返回False 代表臨界區(qū)已占用且不進(jìn)入臨界 區(qū)。運用這個函數(shù),線程能夠迅速查看它是否可以訪問某個共享資源,如果不能訪問,那么 它可以繼續(xù)執(zhí)行某些其他操作,而不必進(jìn)行等待。 使用TryEnterCriticalSection( ) ,必須判斷其返回值。 當(dāng)你不需要臨界區(qū)時,應(yīng)當(dāng)調(diào)用DeleteCriticalSection( ) 過程刪除臨界區(qū),該函數(shù)多半在窗 體銷毀時或程序終止前執(zhí)行。下面是它的聲明: procedure DeleteCriticalSection(var lpCriticalSection : TRTLCriticalSection); stdcall; 例: type TMyThread = class(TThread) protected procedure Execute; override; public constructor Create; virtual; end; var Form1 : TForm1; CriticalSection : TRTLCriticalSection;//定義臨界區(qū) implementation {$R *.dfm} var tick: Integer = 1; procedure TMyThread.Execute; begin EnterCriticalSection(CriticalSection);//進(jìn)入臨界區(qū) try Form1.Edit1.Text := IntToStr(tick); Inc(tick); Sleep(10); finally LeaveCriticalSection(CriticalSection); //離開臨界區(qū) end; end; constructor TMyThread.Create; begin inherited Create(False); FreeOnTerminate := True; end; procedure TForm1.RzButton1Click(Sender : TObject); var index: Integer; begin for index := 0 to 15 do TMyThread.Create; end; procedure TForm1.FormCreate(Sender : TObject); begin InitializeCriticalSection(CriticalSection); //初始化臨界區(qū) end; procedure TForm1.FormDestroy(Sender : TObject); begin DeleteCriticalSection(CriticalSection); //刪除臨界區(qū) end; (2). 使用TcriticalSection 類 TcriticalSection 是在SyncObjs 單元中定義的類,要使用它需要先uses SyncObjs 。它對上 面的那些臨界區(qū)操作API 函數(shù)進(jìn)行了封裝,簡化并方便了在Delphi 中的使用。例如 TcriticalSection.Enter 其實是調(diào)用了TRTLCriticalSection.Enter 。 使用TcriticalSection 類和一般類差不多,首先實例化TcriticalSection 類。使用的時候只要 在主線程當(dāng)中創(chuàng)建這個臨界對象(注意一定要在需要同步的子線程之外建立這個對象)。 Tcriticalsection 類的構(gòu)造函數(shù)比較簡單,沒有帶參數(shù)。 TcriticalSection.Enter 等效于EnterCriticalSection( ) 。 TcriticalSection.TryEnter 等效于TryEnterCriticalSection( ) 。 TcriticalSection.Leave 等效于LeaveCriticalSection( ) 。 例: //在主線程中定義 var criticalsection : TCriticalsection; criticalsection := TCriticalsection.Create; … //在子線程中使用 criticalsection.Enter; try ... finally criticalsection.Leave; end; 警告:臨界區(qū)只有在所有的線程都使用它來訪問全局內(nèi)存時才起作用,如果有線程直接調(diào)用 內(nèi)存,而不通過臨界區(qū),也會造成同時訪問的問題。 注意:臨界區(qū)主要是為實現(xiàn)線程之間同步的,但是使用的時候要注意,一定要在使用臨界區(qū) 同步的線程之外建立該臨界區(qū)(一般在主線程中定義臨界區(qū)并初始化臨界區(qū))。臨界區(qū)是一 個進(jìn)程里的所有線程同步的最好辦法,它不是系統(tǒng)級的,只是進(jìn)程級的,也就是說它可能利 用進(jìn)程內(nèi)的一些標(biāo)志來保證該進(jìn)程內(nèi)的線程同步,據(jù)Richter 說是一個記數(shù)循環(huán)。臨界區(qū)只 能在同一進(jìn)程內(nèi)使用。 2. Mutex 互斥 互斥是在序列化訪問資源時使用操作系統(tǒng)內(nèi)核對象的一種方式。我們首先設(shè)置一個互斥對 象,然后訪問資源,最后釋放互斥對象。在設(shè)置互斥時,如果另一個線程(或進(jìn)程)試圖設(shè) 置相同的互斥對象,該線程將會停下來,直到前一個線程(或進(jìn)程)釋放該互斥對象為止。 注意它可以由不同應(yīng)用程序共享?;コ獾男Ч浅n愃朴谂R界區(qū),除了兩個關(guān)鍵的區(qū)別:首 先,互斥可用于跨進(jìn)程的線程同步。其次,互斥對象能被賦予一個字符串名字,并且通過引 用此名字創(chuàng)建現(xiàn)有內(nèi)核對象的附加句柄。線程同步使用臨界區(qū),進(jìn)程同步使用互斥。 當(dāng)一個互斥對象不再被一個線程所擁有, 它就處于發(fā)信號狀態(tài)。此時首先調(diào)用 WaitForSingleObject( ) 函數(shù)(實現(xiàn)WaitFor 功能的API 還有幾個,這是最簡單的一個)的線 程就成為該互斥對象的擁有者,將互斥對象設(shè)為不發(fā)信號狀態(tài)。當(dāng)線程調(diào)用ReleaseMutex( ) 函數(shù)并傳遞一個互斥對象的句柄作為參數(shù)時,這種擁有關(guān)系就被解除,互斥對象重新進(jìn)入發(fā) 信號狀態(tài)。 提示:臨界區(qū)和互斥的作用類似,都是用來進(jìn)行同步的,但它們間有以下一點差別。臨界區(qū) 只能在進(jìn)程內(nèi)使用,也就是說只能是進(jìn)程內(nèi)的線程間的同步;而互斥則還可用在進(jìn)程之間的; 臨界區(qū)隨著進(jìn)程的終止而終止,而互斥,如果你不用CloseHandle( ) 的話,在進(jìn)程終止后 仍然在系統(tǒng)內(nèi)存在,也就是說它是操作系統(tǒng)全局內(nèi)核對象;臨界區(qū)與互斥最大的區(qū)別是在性 能上,臨界區(qū)在沒有線程沖突時,要用10 ~ 15 個時間片,而互斥由于涉及到系統(tǒng)內(nèi)核要用 400 ~ 600 個時間片;臨界區(qū)不是內(nèi)核對象,它不由操作系統(tǒng)的低級部件管理,而且不能使 用句柄來操縱,而互斥屬于操作系統(tǒng)內(nèi)核對象。 (1). 使用CreateMutex( ) API 函數(shù) 調(diào)用函數(shù)CreateMutex( ) 來創(chuàng)建一個互斥。下面是函數(shù)的聲明: function CreateMutex(lpMutexAttributes: PSecurityAttributes; bInitialOwner: BOOL; lpName: PWideChar): THandle; stdcall; lpMutexAttributes 參數(shù)為一個指向TsecurityAttributtes 記錄的指針。此參數(shù)通常設(shè)為nil , 表示默認(rèn)的安全屬性。bInitalOwner 參數(shù)表示創(chuàng)建互斥的線程是否要成為此互斥對象的初始 擁有者,當(dāng)此參數(shù)為False 時,表示互斥對象沒有擁有者。lpName 參數(shù)指定互斥對象的名 稱,該名稱是大小寫區(qū)分的,設(shè)為nil 表示無命名,如果參數(shù)不是設(shè)為nil ,函數(shù)會搜索 是否有同名的互斥對象存在,如果有,函數(shù)就會返回同名互斥對象的句柄。否則,就新創(chuàng)建 一個互斥對象并返回其句柄。 當(dāng)使用完互斥時,應(yīng)當(dāng)調(diào)用CloseHandle( ) 來關(guān)閉它。 WaitForSingleObject( ) 函數(shù)的使用: 在線程中使用WaitForSingleObject( ) 來防止其他線程進(jìn)入同步區(qū)域的代碼。第一個調(diào)用 WaitForSingleObject( ) 函數(shù)的線程會將事件對象(不限于互斥對象)設(shè)為無信號狀態(tài),其它線 程調(diào)用WaitForSingleObject( ) 函數(shù)時會檢查事件對象是否處于發(fā)信號狀態(tài),這時狀態(tài)處于 無信號狀態(tài),所以其它線程會掛起等待而不執(zhí)行同步區(qū)域中的代碼。當(dāng)?shù)谝粋€線程執(zhí)行完同 步代碼后會釋放事件對象,事件對象重新進(jìn)入發(fā)信號狀態(tài)并喚醒等待線程,其它線程會再次 將事件對象設(shè)為無信號狀態(tài),防止另外的線程執(zhí)行同步代碼。這就實現(xiàn)了線程同步。 此函數(shù)聲明如下: function WaitForSingleObject(hHandle : THandle; dwMilliseconds : DWORD): DWORD; stdcall; 這個函數(shù)可以使當(dāng)前線程在dwMilliseconds 參數(shù)指定的時間內(nèi)等待事件對象信號,直到 hHandle 參數(shù)指定的事件對象進(jìn)入發(fā)信號狀態(tài)為止。當(dāng)一個事件對象不再被線程擁有時,它 就進(jìn)入發(fā)信號狀態(tài)。當(dāng)一個進(jìn)程要終止時,它就進(jìn)入發(fā)信號狀態(tài)。dwMilliseconds 參數(shù)設(shè)為 0 ,這意味著只檢查hHandle 參數(shù)指定的事件對象是否處于發(fā)信號狀態(tài),而后立即返回該 信號狀態(tài)。dwMilliseconds 參數(shù)設(shè)為INFINITE ,表示如果信號不出現(xiàn)將一直等下去。 WaitForSingleObject( ) 在一個指定時間(dwMilliseconds)內(nèi)等待一個事件對象變?yōu)橛行盘枺?br> 在此時間內(nèi),若等待的事件對象一直是無信號的,則調(diào)用線程將處于掛起狀態(tài),否則繼續(xù)執(zhí) 行。超過此時間后,線程繼續(xù)運行。 WaitForSingleObject( ) 函數(shù)返回值及含義: WAIT_ABANDONED 指定的對象是一個事件對象,該對象沒有被擁有線程在線程結(jié)束前釋 放。此時就稱事件對象被拋棄?;コ鈱ο蟮乃袡?quán)被同意授予調(diào)用該函數(shù)的線程?;コ鈱ο?br> 被設(shè)置成為無信號狀態(tài) WAIT_OBJECT_0 指定的對象處于發(fā)信號狀態(tài) WAIT_TIMEOUT 等待的時間已過,對象仍然是非發(fā)信號狀態(tài) WAIT_FAILED 語句出錯 WaitForMultipleObjects( ) 函數(shù)的使用: WaitForMultipleObjects( ) 與WaitForSingleObject( ) 類似,只是它要么等待指定列表(由 lpHandles 指定)中若干個互斥對象(由nCount 決定)都變?yōu)橛行盘?,要么等待一個列表 (由lpHandles 指定)中的一個對象變?yōu)橛行盘枺ㄓ蒪WaitAll 決定)。該函數(shù)聲明如下: function WaitForMultipleObjects(nCount: DWORD; lpHandles: PWOHandleArray; bWaitAll: BOOL; dwMilliseconds: DWORD): DWORD; stdcall; nCount 參數(shù)表示句柄的數(shù)量,最大值為MAXIMUM_WAIT_OBJECTS(64),lpHandles 參數(shù) 是指向句柄數(shù)組的指針,lpHandles 類型可以為(Event,Mutex,Process,Thread,Semaphore) 數(shù)組,bWaitAll 參數(shù)表示等待的類型,如果為True 則等待所有信號量有效再往下執(zhí)行,設(shè) 為False 則當(dāng)有其中一個信號量有效時就向下執(zhí)行,dwMilliseconds 參數(shù)表示超時時間,超 時后向下繼續(xù)執(zhí)行。 注意: 除WaitForSingleObject( ) 和WaitForMultipleObjects( ) 外, 你還可以使用 MsgWaitForMultipleObjects( ) 函數(shù)。該函數(shù)的詳細(xì)情況請看Win32 API 聯(lián)機文檔。 WaitForSingleObject( ) 不僅僅用于互斥,也用于信號量或事件,因此這里用詞為“事件對象” 而非互斥對象。在互斥例中,可以用互斥對象代替事件對象,同樣,在信號量例中,也能以 信號量對象代替事件對象。 再次提示,當(dāng)一個互斥對象不再被一個線程所擁有,它就處于發(fā)信號狀態(tài)。此時首先調(diào)用 WaitForSingleObject( ) 函數(shù)的線程就成為該互斥對象的擁有者,此互斥對象設(shè)為無信號狀 態(tài)。當(dāng)線程調(diào)用ReleaseMutex( ) 函數(shù)并傳遞一個互斥對象的句柄作為參數(shù)時,這種擁有關(guān) 系就被解除,互斥對象重新進(jìn)入發(fā)信號狀態(tài)。ReleaseMutex( ) 聲明如下: function ReleaseMutex(hMutex: THandle): BOOL; stdcall; 進(jìn)程間需要同步時,只需要執(zhí)行CreateMutex( ) 建立一個互斥對象,需要同步的時候只需 要WaitForSingleObject(mutexhandle, INFINITE) ,釋放時只需要ReleaseMutex(mutexhandle) 即可。 例: //先在主線程中創(chuàng)建互斥對象 var hMutex : THandle = 0;//定義一個句柄 ... hMutex := CreateMutex(nil, False, nil);//創(chuàng)建互斥對象,并返回其句柄 //在子線程的Execute 方法中加入以下代碼 WaitForSingleObject(hMutex, INFINITE);//互斥對象處于發(fā)信號狀態(tài)時進(jìn)入同步區(qū),否則等待 ... ReleaseMutex(hMutex); //最后記得要在主線程中釋放互斥對象 CloseHandle(hMutex);//關(guān)閉句柄 (2). 使用TMutex 類 TMutex 是在SyncObjs 單元中定義的類,其是ThandleObject 類的子類,要使用它需要先 uses SyncObjs 。它對上面的那些互斥操作API 函數(shù)進(jìn)行了封裝,簡化并方便了在Delphi 中的使用。 使用前先實例化TMutex 類,其有多個重載的構(gòu)造函數(shù)。聲明如下: constructor Create(UseCOMWait: Boolean = False); overload; constructor Create(MutexAttributes: PSecurityAttributes; InitialOwner: Boolean; const Name: string; UseCOMWait: Boolean = False); overload; constructor Create(DesiredAccess: LongWord; InheritHandle: Boolean; const Name: string; UseCOMWait: Boolean = False); overload; 其實簡單的直接調(diào)用TMutex.Create 就可以返回一個TMutex 對象。 第一個版本將創(chuàng)建一個無名的、使用默認(rèn)安全屬性、創(chuàng)建其的線程非互斥對象的初始擁有者 的TMutex 對象,其中的參數(shù)UseCOMWait 設(shè)為True 時表示當(dāng)某個線程阻塞且等待互斥 對象時,任何單線程單元( STA ) COM 組件調(diào)用可以發(fā)回到該線程,其默認(rèn)為False 。 第二個版本的MutexAttributes 參數(shù)通常設(shè)為nil 表示使用默認(rèn)的安全屬性。InitialOwner 參 數(shù)表示創(chuàng)建線程是否是互斥對象的初始擁有者。Name 參數(shù)表示互斥對象的名字,大小寫區(qū) 分。 第三個版本的DesiredAccess 參數(shù)表示訪問互斥的方式,如果傳遞的訪問方式?jīng)]有被允許那 么構(gòu)造函數(shù)會失敗,其參數(shù)可以是下面幾個常量的任意組合: MUTEX_ALL_ACCESS, MUTEX_MODIFY_STATE, SYNCHRONIZE, _DELETE, READ_CONTROL , WRITE_DAC , WRITE_OWNER 。但任何組合必須包含 SYNCHRONIZE 訪問權(quán)。InheritHandle 參數(shù)表示子進(jìn)程是否可繼承該互斥對象句柄。 TMutex.Acquire 等效于WaitForSingleObject(mutexhandle, INFINITE) ,其實際上就是執(zhí)行 THandleObject.WaitFor(INFINITE)。 TMutex.Release 實際上就是執(zhí)行ReleaseMutex(mutexhandle)。 TMutex.Acquire 只能無限期等待一個互斥對象,要設(shè)置等待時間或等待多個互斥對象要使 用TMutex.WaitFor( ) 或TMutex.WaitForMultiple( )。 WaitFor( ) 是定義在TMutex 的父類ThandleObject 中的虛函數(shù),聲明如下: function WaitFor(Timeout: LongWord): TWaitResult; virtual; 其中返回值枚舉型TWaitResult 可以指示操作結(jié)果,wrSignaled 代表信號已set , wrTimeOut 代表超時且信號未set ,wrAbandoned 代表超時前事件對象被銷毀,wrError 代 表等待時出錯。 WaitForMultiple( ) 是定義在TMutex 的父類ThandleObject 中的類函數(shù),聲明如下: class function WaitForMultiple(const HandleObjs: THandleObjectArray; Timeout: LongWord; AAll: Boolean; out SignaledObj: THandleObject; UseCOMWait: Boolean = False; Len: Integer = 0): TWaitResult; 其中HandleObjs 參數(shù)是包含了要等待的一系列事件對象的數(shù)組,AAll 參數(shù)設(shè)為True 時, 當(dāng)所有事件對象都進(jìn)入發(fā)信號狀態(tài)后該函數(shù)調(diào)用才會完成,當(dāng)返回值為wrSignaled 且 AAll 參數(shù)設(shè)為False 時,第一個發(fā)信號的事件對象會被傳給SignaledObj 參數(shù),Len 參數(shù) 設(shè)置監(jiān)視事件對象的數(shù)量。 注意:WaitFor( ) 和WaitForMultiple( ) 均定義在ThandleObject 類中,而ThandleObject 類 是TMutex 、TSemaphore 、TEvent 類的父類,所以在描述WaitFor( ) 和WaitForMultiple( ) 時使用的是事件對象而非互斥對象或信號量對象。 3. Semaphore 信號量 另一種使線程同步的技術(shù)是使用信號量對象。它是在互斥的基礎(chǔ)上建立的,它與互斥相似, 但它可以計數(shù)。信號量增加了資源計數(shù)的功能,預(yù)定數(shù)目的線程允許同時進(jìn)入要同步的代碼。 例如可以允許一個給定資源同時被三個線程訪問。其實互斥就是最大計數(shù)為1 的信號量。 信號量的使用和互斥差不多。 (1). 使用CreateSemaphore( ) API 函數(shù) 可以用CreateSemaphore( ) 來創(chuàng)建一個信號量對象,其聲明如下: function CreateSemaphore(lpSemaphoreAttributes: PSecurityAttributes; lInitialCount, lMaximumCount: Longint; lpName: PWideChar): THandle; stdcall; 和CreateMutex( ) 函數(shù)一樣, CreateSemaphore( ) 的第一個參數(shù)也是一個指向 TSecurityAttributes 記錄的指針,此參數(shù)的缺省值可以設(shè)為nil 。 lInitialCount 參數(shù)用來指定一個信號量的初始計數(shù)值,這個值必須在0 和lMaximumCount 之間。此參數(shù)大于0 ,就表示信號量處于發(fā)信號狀態(tài)。參數(shù)lMaximumCount 指定計數(shù)值 的最大值。如果這個信號量代表某種資源,那么這個值代表可用資源總數(shù)。 參數(shù)lpName 用于給出信號量對象的名稱,它類似于CreateMutex( ) 函數(shù)的lpName 參數(shù)。 在程序中使用WaitForSingleObject( ) 來防止其他線程進(jìn)入同步區(qū)域的代碼。當(dāng)調(diào)用 WaitForSingleObject( ) 函數(shù)( 或其他WaitFor 函數(shù)) 時, 此計數(shù)值就減1 。當(dāng)調(diào)用 ReleaseSemaphore( ) 時,此計數(shù)值加1 ,此時同步區(qū)域代碼可以被其它線程訪問。其聲明 如下: function ReleaseSemaphore(hSemaphore: THandle; lReleaseCount: Longint; lpPreviousCount: Pointer): BOOL; stdcall; 其中hSemaphore 參數(shù)是創(chuàng)建的信號量句柄,lReleaseCount 參數(shù)是釋放時要增加的信號量 計數(shù),lpPreviousCount 參數(shù)是通過該指針參數(shù)來獲得釋放前的信號量計數(shù),如果不用設(shè)為 nil 。 當(dāng)使用完信號量時,應(yīng)當(dāng)調(diào)用CloseHandle( ) 來關(guān)閉它。 注意:一般的同步使用互斥,是因為其有一個特別之處,當(dāng)一個持有互斥的線程DOWN 掉 的時候,互斥可以自動讓其它等待這個對象的線程接受,而其它的內(nèi)核對象則不具體這個功 能。之所以要使用信號量則是因為其可以提供一個活動線程的上限,即lMaximumCount 參 數(shù),這才是它的真正有用之處。 例: var Form1 : TForm1; HSem : THandle = 0;//定義一個信號量 implementation var tick : Integer = 0; procedure TMyThread.Execute; var WaitReturn : DWord ; begin WaitReturn := WaitForSingleObject(HSem, INFINITE);//使用信號量對象,信號量減1 Form1.Edit1.Text := IntToStr(tick); Inc(tick); Sleep(10); ReleaseSemaphore(HSem, 1, Nil);//釋放信號量對象,信號量加1 end; … procedure TForm1.FormCreate(Sender: TObject); begin HSem := CreateSemaphore(Nil, 1, 1, Nil);//創(chuàng)建信號量對象 end; procedure TForm1.FormDestroy(Sender: TObject); begin CloseHandle(HSem);//銷毀信號量 end; procedure TForm1.Button1Click(Sender: TObject); var index : Integer; begin for index := 0 to 10 do TMyThread.Create; end; (2). 使用TSemaphore 類 TSemaphore 是在SyncObjs 單元中定義的類,其是ThandleObject 類的子類,要使用它需 要先uses SyncObjs 。它對上面的API 函數(shù)進(jìn)行了封裝,簡化并方便了在Delphi 中的使 用。 其有三個版本的構(gòu)造器,簡單執(zhí)行TSemaphore.Create 就可實例化一個對象: constructor Create(UseCOMWait: Boolean = False); overload; constructor Create(SemaphoreAttributes: PSecurityAttributes; AInitialCount: Integer; AMaximumCount: Integer; const Name: string; UseCOMWait: Boolean = False); overload; constructor Create(DesiredAccess: LongWord; InheritHandle: Boolean; const Name: string; UseCOMWait: Boolean = False); overload; 參數(shù)參見上面介紹。 TSemaphore.Acquire 等效于WaitForSingleObject(semaphorehandle, INFINITE) ,其實際上就 是執(zhí)行THandleObject.WaitFor(INFINITE)?;蛘呤褂肳aitFor( ) 和WaitForMultiple( ) 函數(shù), 這兩個函數(shù)可以設(shè)置等待的時間或等待多個事件對象。 TSemaphore.Release 有兩個版本,聲明如下: procedure Release; override; overload; function Release(AReleaseCount: Integer): Integer; overload; reintroduce; 第一個版本實際執(zhí)行ReleaseSemaphore(FHandle, 1, nil) 第二個版本AReleaseCount 參數(shù)表示釋放時增加的信號量計數(shù)值,返回值是釋放前的信號 量計數(shù)值。實際執(zhí)行ReleaseSemaphore(FHandle, AReleaseCount, @Result),其中@Result 是 指向Release 函數(shù)返回值Integer 類型的指針。如果要指定增加計數(shù)值應(yīng)使用第二個版本。 4. Event 事件 事件( Event )與Delphi 中的事件有所不同。從本質(zhì)上說,Event 其實相當(dāng)于一個全局的布 爾變量。它有兩個賦值操作: SetEvent 和ResetEvent ,相當(dāng)于把它設(shè)置為True 或False 。 而檢查它的值是通過WaitForSingleObject( ) (或其它WaitFor 函數(shù))操作進(jìn)行。SetEvent 和 ResetEvent 操作是原語操作,所以Event 可以實現(xiàn)一般布爾變量不能實現(xiàn)的在多線程中的 應(yīng)用。 當(dāng)Event 從Reset 狀態(tài)向Set 狀態(tài)轉(zhuǎn)換時,喚醒其它掛起的線程,這就是它為什么叫 Event 的原因。所謂“事件”就是指“狀態(tài)的轉(zhuǎn)換”。通過Event 可以在線程間傳遞這種“狀 態(tài)轉(zhuǎn)換”信息。所以其本質(zhì)是用來通知某事已經(jīng)發(fā)生的信號,在這里可用來表示共享資源已 經(jīng)在使用或已經(jīng)使用完的信號。 (1). 使用CreateEvent( ) API 函數(shù) 使用CreateEvent( ) 創(chuàng)建一個事件,聲明如下: function CreateEvent(lpEventAttributes: PSecurityAttributes; bManualReset, bInitialState: BOOL; lpName: PWideChar): THandle; stdcall; 其中bManualReset 參數(shù)代表創(chuàng)建的Event 是自動復(fù)位還是人工復(fù)位,如果設(shè)為True 表示 人工復(fù)位,一旦該Event 被設(shè)置為有信號,則它一直會等到手動執(zhí)行ResetEvent( ) 時才會 變?yōu)闊o信號,設(shè)為False 表示自動復(fù)位,Event 被設(shè)置為有信號時,則當(dāng)有一個線程執(zhí)行 WaitForSingleObject( ) 時該Event 就會自動復(fù)位,變成無信號。bInitialState 參數(shù)代表事件 的初始狀態(tài),設(shè)為True,事件創(chuàng)建后為有信號,設(shè)為False 則為無信號。 不同于互斥或信號量,Event 不使用Release 相關(guān)函數(shù)設(shè)置相關(guān)對象進(jìn)入發(fā)信號狀態(tài),而使 用SetEvent( ) 函數(shù),當(dāng)線程執(zhí)行完同步代碼要從同步區(qū)域中離開時應(yīng)執(zhí)行該函數(shù),聲明如 下: function SetEvent(hEvent: THandle): BOOL; stdcall; 當(dāng)事件創(chuàng)建為人工復(fù)位時,在線程進(jìn)入同步區(qū)域執(zhí)行同步代碼前應(yīng)執(zhí)行ResetEvent( ) 函數(shù), 將Event 設(shè)為無信號。聲明如下: function ResetEvent(hEvent: THandle): BOOL; stdcall; PulseEvent( ) 是一個比較有意思的方法,正如名字,它使一個Event 對象的狀態(tài)發(fā)生一次 脈沖變化,將無信號設(shè)為有信號,喚醒等待的線程,再設(shè)為無信號,而整個操作是原子的。 對自動復(fù)位的Event 對象,它僅喚醒第一個等到該事件的線程(如果有的話),而對于人工復(fù) 位的Event 對象,它喚醒所有等待的線程。聲明如下: function PulseEvent(hEvent: THandle): BOOL; stdcall; 當(dāng)使用完事件時,應(yīng)當(dāng)調(diào)用CloseHandle( ) 來關(guān)閉它。 (2). 使用TEvent 類 TEvent 是在SyncObjs 單元中定義的類,其是ThandleObject 類的子類,要使用它需要先 uses SyncObjs 。它對上面的API 函數(shù)進(jìn)行了封裝,簡化并方便了在Delphi 中的使用。 TEvent 若在多線程環(huán)境中可用于與其它線程同步;若在單線程環(huán)境中可用于調(diào)整響應(yīng)不同 異步事件(如系統(tǒng)消息或用戶動作)的代碼段。構(gòu)造函數(shù)如下: constructor Create(EventAttributes: PSecurityAttributes; ManualReset: Boolean; InitialState: Boolean; const Name: string; UseCOMWait: Boolean = False); overload; constructor Create(UseCOMWait: Boolean = False); overload; ManualReset 參數(shù)為是否手工復(fù)位,InitialState 參數(shù)為初始狀態(tài)。 TEvent.SetEvent( ) 和TEvent.ResetEvent( ) 均無參數(shù)。 TEvent 類中沒有定義與PulseEvent 功能一樣的方法。 TEvent 類同樣可以使用WaitFor( ) 和WaitForMultiple( ) 函數(shù)。 但要注意的是,TEvent 類并沒有實現(xiàn)Acquire 函數(shù),該函數(shù)是定義在TSynchroObject 類 中僅作為接口、沒有執(zhí)行代碼的虛函數(shù)。TSynchroObject 是ThandleObject 類的父類。其實 自己實現(xiàn)Acquire 函數(shù)也不難,它實際上是執(zhí)行THandleObject.WaitFor(INFINITE) 函數(shù), 仿照上面的TMutex 類寫就可以。 另外,Delphi 中定義了一個更簡單的事件類,TSimpleEvent 類,但從源代碼上看,該類僅 有TSimpleEvent = class(TEvent); 一句,并未定義任何屬于TSimpleEvent 的成員。估計是 作為向后兼容而存在。 5. Global Atom 全局原子 Windows 系統(tǒng)中,為了實現(xiàn)信息共享,系統(tǒng)維護了一張全局原子表( Global Atom Table ), 用于保存字符串與之對應(yīng)的標(biāo)志符(原子)的組合,系統(tǒng)能保證其中的每個原子都是唯一的, 管理其引用計數(shù),并且當(dāng)該全局原子的引用計數(shù)為0 時,從內(nèi)存中清除。應(yīng)用程序在原子表 中可以放置字符串,并接收一個16 位整數(shù)值(叫做原子,即Atom ),它可以用來提取該字 符串。放在原子表中的字符串叫做原子的名字。系統(tǒng)提供了許多原子表。每個表有不同的目 的。例如,動態(tài)數(shù)據(jù)交換( DDE )應(yīng)用程序使用全局原子表與其他應(yīng)用程序共享項目名稱和 主題名稱字符串,不傳遞實際的字符串,一個DDE 應(yīng)用程序傳遞全局原子給它的父進(jìn)程, 父進(jìn)程使用原子提取原子表中的字符串,這就是利用全局原子進(jìn)行進(jìn)程或線程間的數(shù)據(jù)交 換;使用全局原子也可防止多次啟動某個程序。 應(yīng)用程序可以使用本地原子表來有效地管理大量只用于程序內(nèi)部的字符串。這些字符串,以 及相關(guān)聯(lián)的原子,只對創(chuàng)建該原子表的應(yīng)用程序可用。一個在許多數(shù)據(jù)結(jié)構(gòu)中需要相同字符 串的應(yīng)用程序,可以通過使用本地原子表來減少內(nèi)存使用。程序可以把字符串放入原子表, 把相關(guān)的原子放入結(jié)構(gòu),而無需把字符串拷到每個結(jié)構(gòu)中。這樣,一個字符串在內(nèi)存中只出 現(xiàn)一次,但可以在程序中多次使用。應(yīng)用程序也可以使用本地原子表來快速搜索特定的字符 串。要實現(xiàn)這樣的搜索,程序只需把要搜索的字符串放入原子表中,然后把結(jié)果原子與相關(guān) 數(shù)據(jù)結(jié)構(gòu)中的原子相比較。通常情況下,比較原子要比比較字符串要快得多。原子表是用哈 希表實現(xiàn)的。默認(rèn)時,一個本地原子表使用37 個bucket 的哈希表。不過,你可以通過調(diào) 用InitAtomTable 函數(shù)來改變bucket 數(shù)量。如果程序準(zhǔn)備調(diào)用InitAtomTable ,那它必須 在調(diào)用任何其他原子管理函數(shù)前調(diào)用它。這里只簡單介紹本地原子表。它有多個相關(guān)的函數(shù), function InitAtomTable(nSize: DWORD): BOOL; stdcall; function DeleteAtom(nAtom: ATOM): ATOM; stdcall; function AddAtom(lpString: PWideChar): ATOM; stdcall; function FindAtom(lpString: PWideChar): ATOM; stdcall; function GetAtomName(nAtom: ATOM; lpBuffer: PWideChar; nSize: Integer): UINT; stdcall; 以下介紹全局原子表相關(guān)函數(shù)。 function GlobalAddAtom(lpString: PWideChar): ATOM; stdcall; 增加一個字符串到全局原子表中,并返回一個唯一標(biāo)識值。 lpString 參數(shù)為要添加到全局原子表中的字符串。 如果成功返回新增加的全局原子,失敗則返回0 。ATOM 類型等于Word 類型。 function GlobalDeleteAtom(nAtom: ATOM): ATOM; stdcall; 減少對指定全局原子的引用計數(shù),引用計數(shù)減1 ,如果引用計數(shù)為零,系統(tǒng)會在全局原子 表中刪除此原子。 此函數(shù)一直返回0 。 只要全局原子的引用計數(shù)大于0 ,其原子名稱將保留在全局原子表中,即使把它放入表中 的應(yīng)用程序終結(jié)了。一個本地的原子表在應(yīng)用程序終結(jié)時被銷毀,而不管其中原子的引用計 數(shù)是多少。 function GlobalFindAtom(lpString: PWideChar): ATOM; stdcall; 在全局原子表中查找是否存在指定字符串。 lpString 參數(shù)為要查找的字符串。 如果在全局原子表中存在要查找的字符串,則返回此字符串對應(yīng)的原子,沒有找到則返回0。 function GlobalGetAtomName(nAtom: ATOM; lpBuffer: PWideChar; nSize: Integer): UINT; stdcall; 返回指定原子所對應(yīng)的字符串。 nAtom 參數(shù)為指定查找的原子,lpBuffer 參數(shù)為要存放字符串的緩沖區(qū),nSize 參數(shù)為緩沖 區(qū)大小。 若操作成功返回緩沖區(qū)接受長度,若失敗返回0 。UINT 類型等于LongWord 類型。 例: //在程序的program 文件中 ... if GlobalFindAtom(iAtom) = 0 then begin Application.Initialize; Application.CreateForm(TForm1, Form1); Application.Run; end else MessageBox(0, '已經(jīng)有一個程序在運行', ' ', mb_OK); ... 6. Synchronize 同步 Synchronize( ) 是定義在TThread 類中的函數(shù),它可以讓要執(zhí)行的代碼實現(xiàn)線程同步,但這 種同步其實是偽同步,其原理是將子線程要執(zhí)行的代碼通過消息傳遞給主線程,由主線程來 執(zhí)行,主線程將代碼放在一個隱蔽的窗口里運行,而子線程會等待主線程將執(zhí)行結(jié)果發(fā)給它, 這樣的話,這段代碼就不是子線程代碼,而是一般的主線程代碼。Synchronize( ) 只是將該 線程的代碼放到主線程中運行,并非實際意義的線程同步。RAD Studio VCL Reference 中也 描述為:Executes a method call within the main thread,Synchronize causes the call specified by AMethod(參數(shù)) to be executed using the main thread,,thereby avoiding multi-thread conflicts。 這里有一個問題,如果Synchronize( ) 執(zhí)行的代碼很繁忙,例如執(zhí)行的代碼運算過于復(fù)雜、 龐大或者從數(shù)據(jù)庫中取出大量數(shù)據(jù),數(shù)據(jù)庫不會立即返回數(shù)據(jù)時或者使用ADO 組件連接 數(shù)據(jù)庫,而這時數(shù)據(jù)庫無法連接,ADO 組件需要超時才會終止運行,這些都會導(dǎo)致主窗口 會阻塞掉,看似死機一般。因此,通常對用戶界面類VCL 組件的訪問才使用Synchronize( ) 函數(shù),一般用戶界面類VCL 組件都由主線程創(chuàng)建、存在于主窗口中,而且對VCL 組件的 訪問或修改的執(zhí)行效率都比較高,不會過多的影響性能。絕對不能在主線程中執(zhí)行 Synchronize( ) 函數(shù),這會導(dǎo)致無限循環(huán)。 Synchronize( ) 函數(shù)一般在線程的Execute 函數(shù)中調(diào)用。其有四個版本,兩個是類函數(shù),兩 個是靜態(tài)函數(shù),聲明如下: class procedure Synchronize(AThread: TThread; AMethod: TThreadMethod); overload; class procedure Synchronize(AThread: TThread; AThreadProc: TThreadProcedure); overload; procedure Synchronize(AMethod: TThreadMethod); overload; procedure Synchronize(AThreadProc: TThreadProcedure); overload; AThread 參數(shù)是當(dāng)前線程,TThreadMethod 是對象的函數(shù)指針類型,TThreadProcedure 是匿 名函數(shù)類型。 注意:Synchronize( ) 的AMethod 或AThreadProc 參數(shù)必須是一個無參數(shù)的procedure , 故在此procedure 中無法傳遞參數(shù)值,通常的解決方法是在線程類中增加額外的成員,用其 代替參數(shù)來傳遞信息。 例: type TMyThread = class(TThread) str : string;//額外的域,代替參數(shù)將字符串寫入Memo ... procedure TMyThread.WriteMemo; begin Memo.Lines.Add(str); end; ... procedure TMyThread.Execute; begin str := 'Hello'; synchronize(WriteMemo); end; 上次跟大家分享了線程的標(biāo)準(zhǔn)代碼,其實在線程的使用中最重要的是線程的同步問題,如果你在使用線程后,發(fā)現(xiàn)你的界面經(jīng)常被卡死,或者無法顯示出來,顯示混亂,你的使用的變量值老是不按預(yù)想的變化,結(jié)果往往出乎意料,那么你很有可能是忽略了線程同步的問題。 當(dāng)有多個線程的時候,經(jīng)常需要去同步這些線程以訪問同一個數(shù)據(jù)或資源。例如,假設(shè)有一個程序,其中一個線程用于把文件讀到內(nèi)存,而另一個線程用于統(tǒng)計文件中的字符數(shù)。當(dāng)然,在把整個文件調(diào)入內(nèi)存之前,統(tǒng)計它的計數(shù)是沒有意義的。但是,由于每個操作都有自己的 線程,操作系統(tǒng)會把兩個線程當(dāng)作是互不相干的任務(wù)分別執(zhí)行,這樣就可能在沒有把整個文 件裝入內(nèi)存時統(tǒng)計字?jǐn)?shù)。為解決此問題,你必須使兩個線程同步工作。存在一些線程同步地 址的問題,Windows 提供了許多線程同步的方式。在本節(jié)您將看到使用臨界區(qū)、互斥、信 號量、事件、全局原子和Synchronize 函數(shù)來解決線程同步的問題。 下面的同步技術(shù)一般均有兩種使用方式,一種是直接使用Windows API 函數(shù),一種是使用 由Delphi 對API 函數(shù)進(jìn)行封裝的類。 以下函數(shù)以Delphi 2009 中的函數(shù)格式為準(zhǔn)。 1. Critical Sections 臨界區(qū) 臨界區(qū)是一種最直接的線程同步方式。所謂臨界區(qū),就是一次只能由一個線程來執(zhí)行的一段 代碼。例如把初始化數(shù)組的代碼放在臨界區(qū)內(nèi),另一個線程在第一個線程處理完之前是不會 被執(zhí)行的。臨界區(qū)非常適合于序列化對一個進(jìn)程中的數(shù)據(jù)的訪問,因為它們的速度很快。 (1). 使用EnterCriticalSection( ) 和LeaveCriticalSection( ) API 函數(shù) 在使用臨界區(qū)之前, 必須定義一個TRTLCriticalSection 類型的記錄變量并使用 InitializeCriticalSection( ) 過程來初始化臨界區(qū)。該過程多半在窗體創(chuàng)建時或在程序初始化時 執(zhí)行。 其聲明如下: procedure InitializeCriticalSection(var lpCriticalSection : TRTLCriticalSection);stdcall; lpCriticalSection 參數(shù)是一個TRTLCriticalSection 類型的記錄, 并且是變參。至于 TRTLCriticalSection 是如何定義的,這并不重要,因為很少需要查看這個記錄中的具體內(nèi)容。 只需要在lpCriticalSection 中傳遞未初始化的記錄, InitializeCriticalSection( ) 過程就會填 充這個記錄。 注意:Microsoft 故意隱瞞了TRTLCriticalSection 的細(xì)節(jié)。因為,其內(nèi)容在不同的硬件平臺 2 上是不同的。在基于Intel 的平臺上,TRTLCriticalSection 包含一個計數(shù)器、一個指示當(dāng)前 線程句柄的域和一個系統(tǒng)事件的句柄。在Alpha 平臺上,計數(shù)器被替換為一種Alpha-CPU數(shù)據(jù)結(jié)構(gòu),稱為spinlock 。 在記錄被填充后,我們就可以開始創(chuàng)建臨界區(qū)了。這時我們需要用EnterCriticalSection( ) 和 LeaveCriticalSection( ) 來封裝代碼塊,這兩個函數(shù)分別代表進(jìn)入和離開臨界區(qū),將要同步的 代碼塊放在這兩個函數(shù)中間。在第一個線程調(diào)用了EnterCriticalSection( ) 之后,所有別的 線程就不能再進(jìn)入代碼塊并掛起等待第一個線程離開臨界區(qū)。下一個線程要等第一個線程調(diào) 用LeaveCriticalSection( ) 后才能被喚醒。這兩個過程的聲明如下: procedure EnterCriticalSection(var lpCriticalSection : TRTLCriticalSection);stdcall; //進(jìn)入臨界區(qū) procedure LeaveCriticalSection(var lpCriticalSection : TRTLCriticalSection);stdcall; //離開臨界 區(qū) 正如你所想的,參數(shù)lpCriticalSection 就是由InitializeCriticalSection( ) 填充的記錄。 如果在某個子線程執(zhí)行EnterCriticalSection( ) 前,已經(jīng)有另一個線程進(jìn)入臨界區(qū)且還未離 開臨界區(qū),則該子線程將掛起并無限期等待另一個線程離開臨界區(qū),要想不掛起且0 時間 等待,必須使用TryEnterCriticalSection( ) 。該過程聲明如下: function TryEnterCriticalSection(var lpCriticalSection: TRTLCriticalSection): BOOL; stdcall; TryEnterCriticalSection( ) 不同于EnterCriticalSection( ) 的聲明在于多出一個布爾型的返回 值,如果返回True 代表成功進(jìn)入臨界區(qū),如果返回False 代表臨界區(qū)已占用且不進(jìn)入臨界 區(qū)。運用這個函數(shù),線程能夠迅速查看它是否可以訪問某個共享資源,如果不能訪問,那么 它可以繼續(xù)執(zhí)行某些其他操作,而不必進(jìn)行等待。 使用TryEnterCriticalSection( ) ,必須判斷其返回值。 當(dāng)你不需要臨界區(qū)時,應(yīng)當(dāng)調(diào)用DeleteCriticalSection( ) 過程刪除臨界區(qū),該函數(shù)多半在窗 體銷毀時或程序終止前執(zhí)行。下面是它的聲明: procedure DeleteCriticalSection(var lpCriticalSection : TRTLCriticalSection); stdcall; 例: type TMyThread = class(TThread) protected procedure Execute; override; public constructor Create; virtual; end; var Form1 : TForm1; CriticalSection : TRTLCriticalSection;//定義臨界區(qū) implementation {$R *.dfm} var tick: Integer = 1; 3 procedure TMyThread.Execute; begin EnterCriticalSection(CriticalSection);//進(jìn)入臨界區(qū) try Form1.Edit1.Text := IntToStr(tick); Inc(tick); Sleep(10); finally LeaveCriticalSection(CriticalSection); //離開臨界區(qū) end; end; constructor TMyThread.Create; begin inherited Create(False); FreeOnTerminate := True; end; procedure TForm1.RzButton1Click(Sender : TObject); var index: Integer; begin for index := 0 to 15 do TMyThread.Create; end; procedure TForm1.FormCreate(Sender : TObject); begin InitializeCriticalSection(CriticalSection); //初始化臨界區(qū) end; procedure TForm1.FormDestroy(Sender : TObject); begin DeleteCriticalSection(CriticalSection); //刪除臨界區(qū) end; (2). 使用TcriticalSection 類 TcriticalSection 是在SyncObjs 單元中定義的類,要使用它需要先uses SyncObjs 。它對上 面的那些臨界區(qū)操作API 函數(shù)進(jìn)行了封裝,簡化并方便了在Delphi 中的使用。例如 TcriticalSection.Enter 其實是調(diào)用了TRTLCriticalSection.Enter 。 使用TcriticalSection 類和一般類差不多,首先實例化TcriticalSection 類。使用的時候只要 在主線程當(dāng)中創(chuàng)建這個臨界對象(注意一定要在需要同步的子線程之外建立這個對象)。 Tcriticalsection 類的構(gòu)造函數(shù)比較簡單,沒有帶參數(shù)。 TcriticalSection.Enter 等效于EnterCriticalSection( ) 。 4 TcriticalSection.TryEnter 等效于TryEnterCriticalSection( ) 。 TcriticalSection.Leave 等效于LeaveCriticalSection( ) 。 例: //在主線程中定義 var criticalsection : TCriticalsection; criticalsection := TCriticalsection.Create; … //在子線程中使用 criticalsection.Enter; try ... finally criticalsection.Leave; end; 警告:臨界區(qū)只有在所有的線程都使用它來訪問全局內(nèi)存時才起作用,如果有線程直接調(diào)用 內(nèi)存,而不通過臨界區(qū),也會造成同時訪問的問題。 注意:臨界區(qū)主要是為實現(xiàn)線程之間同步的,但是使用的時候要注意,一定要在使用臨界區(qū) 同步的線程之外建立該臨界區(qū)(一般在主線程中定義臨界區(qū)并初始化臨界區(qū))。臨界區(qū)是一 個進(jìn)程里的所有線程同步的最好辦法,它不是系統(tǒng)級的,只是進(jìn)程級的,也就是說它可能利 用進(jìn)程內(nèi)的一些標(biāo)志來保證該進(jìn)程內(nèi)的線程同步,據(jù)Richter 說是一個記數(shù)循環(huán)。臨界區(qū)只 能在同一進(jìn)程內(nèi)使用。 2. Mutex 互斥 互斥是在序列化訪問資源時使用操作系統(tǒng)內(nèi)核對象的一種方式。我們首先設(shè)置一個互斥對 象,然后訪問資源,最后釋放互斥對象。在設(shè)置互斥時,如果另一個線程(或進(jìn)程)試圖設(shè) 置相同的互斥對象,該線程將會停下來,直到前一個線程(或進(jìn)程)釋放該互斥對象為止。 注意它可以由不同應(yīng)用程序共享?;コ獾男Ч浅n愃朴谂R界區(qū),除了兩個關(guān)鍵的區(qū)別:首 先,互斥可用于跨進(jìn)程的線程同步。其次,互斥對象能被賦予一個字符串名字,并且通過引 用此名字創(chuàng)建現(xiàn)有內(nèi)核對象的附加句柄。線程同步使用臨界區(qū),進(jìn)程同步使用互斥。 當(dāng)一個互斥對象不再被一個線程所擁有, 它就處于發(fā)信號狀態(tài)。此時首先調(diào)用 WaitForSingleObject( ) 函數(shù)(實現(xiàn)WaitFor 功能的API 還有幾個,這是最簡單的一個)的線 程就成為該互斥對象的擁有者,將互斥對象設(shè)為不發(fā)信號狀態(tài)。當(dāng)線程調(diào)用ReleaseMutex( ) 函數(shù)并傳遞一個互斥對象的句柄作為參數(shù)時,這種擁有關(guān)系就被解除,互斥對象重新進(jìn)入發(fā) 信號狀態(tài)。 提示:臨界區(qū)和互斥的作用類似,都是用來進(jìn)行同步的,但它們間有以下一點差別。臨界區(qū) 只能在進(jìn)程內(nèi)使用,也就是說只能是進(jìn)程內(nèi)的線程間的同步;而互斥則還可用在進(jìn)程之間的; 臨界區(qū)隨著進(jìn)程的終止而終止,而互斥,如果你不用CloseHandle( ) 的話,在進(jìn)程終止后 仍然在系統(tǒng)內(nèi)存在,也就是說它是操作系統(tǒng)全局內(nèi)核對象;臨界區(qū)與互斥最大的區(qū)別是在性 能上,臨界區(qū)在沒有線程沖突時,要用10 ~ 15 個時間片,而互斥由于涉及到系統(tǒng)內(nèi)核要用 5 400 ~ 600 個時間片;臨界區(qū)不是內(nèi)核對象,它不由操作系統(tǒng)的低級部件管理,而且不能使 用句柄來操縱,而互斥屬于操作系統(tǒng)內(nèi)核對象。 (1). 使用CreateMutex( ) API 函數(shù) 調(diào)用函數(shù)CreateMutex( ) 來創(chuàng)建一個互斥。下面是函數(shù)的聲明: function CreateMutex(lpMutexAttributes: PSecurityAttributes; bInitialOwner: BOOL; lpName: PWideChar): THandle; stdcall; lpMutexAttributes 參數(shù)為一個指向TsecurityAttributtes 記錄的指針。此參數(shù)通常設(shè)為nil , 表示默認(rèn)的安全屬性。bInitalOwner 參數(shù)表示創(chuàng)建互斥的線程是否要成為此互斥對象的初始 擁有者,當(dāng)此參數(shù)為False 時,表示互斥對象沒有擁有者。lpName 參數(shù)指定互斥對象的名 稱,該名稱是大小寫區(qū)分的,設(shè)為nil 表示無命名,如果參數(shù)不是設(shè)為nil ,函數(shù)會搜索 是否有同名的互斥對象存在,如果有,函數(shù)就會返回同名互斥對象的句柄。否則,就新創(chuàng)建 一個互斥對象并返回其句柄。 當(dāng)使用完互斥時,應(yīng)當(dāng)調(diào)用CloseHandle( ) 來關(guān)閉它。 WaitForSingleObject( ) 函數(shù)的使用: 在線程中使用WaitForSingleObject( ) 來防止其他線程進(jìn)入同步區(qū)域的代碼。第一個調(diào)用 WaitForSingleObject( ) 函數(shù)的線程會將事件對象(不限于互斥對象)設(shè)為無信號狀態(tài),其它線 程調(diào)用WaitForSingleObject( ) 函數(shù)時會檢查事件對象是否處于發(fā)信號狀態(tài),這時狀態(tài)處于 無信號狀態(tài),所以其它線程會掛起等待而不執(zhí)行同步區(qū)域中的代碼。當(dāng)?shù)谝粋€線程執(zhí)行完同 步代碼后會釋放事件對象,事件對象重新進(jìn)入發(fā)信號狀態(tài)并喚醒等待線程,其它線程會再次 將事件對象設(shè)為無信號狀態(tài),防止另外的線程執(zhí)行同步代碼。這就實現(xiàn)了線程同步。 此函數(shù)聲明如下: function WaitForSingleObject(hHandle : THandle; dwMilliseconds : DWORD): DWORD; stdcall; 這個函數(shù)可以使當(dāng)前線程在dwMilliseconds 參數(shù)指定的時間內(nèi)等待事件對象信號,直到 hHandle 參數(shù)指定的事件對象進(jìn)入發(fā)信號狀態(tài)為止。當(dāng)一個事件對象不再被線程擁有時,它 就進(jìn)入發(fā)信號狀態(tài)。當(dāng)一個進(jìn)程要終止時,它就進(jìn)入發(fā)信號狀態(tài)。dwMilliseconds 參數(shù)設(shè)為 0 ,這意味著只檢查hHandle 參數(shù)指定的事件對象是否處于發(fā)信號狀態(tài),而后立即返回該 信號狀態(tài)。dwMilliseconds 參數(shù)設(shè)為INFINITE ,表示如果信號不出現(xiàn)將一直等下去。 WaitForSingleObject( ) 在一個指定時間(dwMilliseconds)內(nèi)等待一個事件對象變?yōu)橛行盘枺?br>在此時間內(nèi),若等待的事件對象一直是無信號的,則調(diào)用線程將處于掛起狀態(tài),否則繼續(xù)執(zhí) 行。超過此時間后,線程繼續(xù)運行。 WaitForSingleObject( ) 函數(shù)返回值及含義: WAIT_ABANDONED 指定的對象是一個事件對象,該對象沒有被擁有線程在線程結(jié)束前釋 放。此時就稱事件對象被拋棄?;コ鈱ο蟮乃袡?quán)被同意授予調(diào)用該函數(shù)的線程?;コ鈱ο?br>被設(shè)置成為無信號狀態(tài) WAIT_OBJECT_0 指定的對象處于發(fā)信號狀態(tài) WAIT_TIMEOUT 等待的時間已過,對象仍然是非發(fā)信號狀態(tài) WAIT_FAILED 語句出錯 WaitForMultipleObjects( ) 函數(shù)的使用: WaitForMultipleObjects( ) 與WaitForSingleObject( ) 類似,只是它要么等待指定列表(由 lpHandles 指定)中若干個互斥對象(由nCount 決定)都變?yōu)橛行盘?,要么等待一個列表 6 (由lpHandles 指定)中的一個對象變?yōu)橛行盘枺ㄓ蒪WaitAll 決定)。該函數(shù)聲明如下: function WaitForMultipleObjects(nCount: DWORD; lpHandles: PWOHandleArray; bWaitAll: BOOL; dwMilliseconds: DWORD): DWORD; stdcall; nCount 參數(shù)表示句柄的數(shù)量,最大值為MAXIMUM_WAIT_OBJECTS(64),lpHandles 參數(shù) 是指向句柄數(shù)組的指針,lpHandles 類型可以為(Event,Mutex,Process,Thread,Semaphore) 數(shù)組,bWaitAll 參數(shù)表示等待的類型,如果為True 則等待所有信號量有效再往下執(zhí)行,設(shè) 為False 則當(dāng)有其中一個信號量有效時就向下執(zhí)行,dwMilliseconds 參數(shù)表示超時時間,超 時后向下繼續(xù)執(zhí)行。 注意: 除WaitForSingleObject( ) 和WaitForMultipleObjects( ) 外, 你還可以使用 MsgWaitForMultipleObjects( ) 函數(shù)。該函數(shù)的詳細(xì)情況請看Win32 API 聯(lián)機文檔。 WaitForSingleObject( ) 不僅僅用于互斥,也用于信號量或事件,因此這里用詞為“事件對象” 而非互斥對象。在互斥例中,可以用互斥對象代替事件對象,同樣,在信號量例中,也能以 信號量對象代替事件對象。 再次提示,當(dāng)一個互斥對象不再被一個線程所擁有,它就處于發(fā)信號狀態(tài)。此時首先調(diào)用 WaitForSingleObject( ) 函數(shù)的線程就成為該互斥對象的擁有者,此互斥對象設(shè)為無信號狀 態(tài)。當(dāng)線程調(diào)用ReleaseMutex( ) 函數(shù)并傳遞一個互斥對象的句柄作為參數(shù)時,這種擁有關(guān) 系就被解除,互斥對象重新進(jìn)入發(fā)信號狀態(tài)。ReleaseMutex( ) 聲明如下: function ReleaseMutex(hMutex: THandle): BOOL; stdcall; 進(jìn)程間需要同步時,只需要執(zhí)行CreateMutex( ) 建立一個互斥對象,需要同步的時候只需 要WaitForSingleObject(mutexhandle, INFINITE) ,釋放時只需要ReleaseMutex(mutexhandle) 即可。 例: //先在主線程中創(chuàng)建互斥對象 var hMutex : THandle = 0;//定義一個句柄 ... hMutex := CreateMutex(nil, False, nil);//創(chuàng)建互斥對象,并返回其句柄 //在子線程的Execute 方法中加入以下代碼 WaitForSingleObject(hMutex, INFINITE);//互斥對象處于發(fā)信號狀態(tài)時進(jìn)入同步區(qū),否則等待 ... ReleaseMutex(hMutex); //最后記得要在主線程中釋放互斥對象 CloseHandle(hMutex);//關(guān)閉句柄 (2). 使用TMutex 類 TMutex 是在SyncObjs 單元中定義的類,其是ThandleObject 類的子類,要使用它需要先 uses SyncObjs 。它對上面的那些互斥操作API 函數(shù)進(jìn)行了封裝,簡化并方便了在Delphi 中的使用。 使用前先實例化TMutex 類,其有多個重載的構(gòu)造函數(shù)。聲明如下: 7 constructor Create(UseCOMWait: Boolean = False); overload; constructor Create(MutexAttributes: PSecurityAttributes; InitialOwner: Boolean; const Name: string; UseCOMWait: Boolean = False); overload; constructor Create(DesiredAccess: LongWord; InheritHandle: Boolean; const Name: string; UseCOMWait: Boolean = False); overload; 其實簡單的直接調(diào)用TMutex.Create 就可以返回一個TMutex 對象。 第一個版本將創(chuàng)建一個無名的、使用默認(rèn)安全屬性、創(chuàng)建其的線程非互斥對象的初始擁有者 的TMutex 對象,其中的參數(shù)UseCOMWait 設(shè)為True 時表示當(dāng)某個線程阻塞且等待互斥 對象時,任何單線程單元( STA ) COM 組件調(diào)用可以發(fā)回到該線程,其默認(rèn)為False 。 第二個版本的MutexAttributes 參數(shù)通常設(shè)為nil 表示使用默認(rèn)的安全屬性。InitialOwner 參 數(shù)表示創(chuàng)建線程是否是互斥對象的初始擁有者。Name 參數(shù)表示互斥對象的名字,大小寫區(qū) 分。 第三個版本的DesiredAccess 參數(shù)表示訪問互斥的方式,如果傳遞的訪問方式?jīng)]有被允許那 么構(gòu)造函數(shù)會失敗,其參數(shù)可以是下面幾個常量的任意組合: MUTEX_ALL_ACCESS, MUTEX_MODIFY_STATE, SYNCHRONIZE, _DELETE, READ_CONTROL , WRITE_DAC , WRITE_OWNER 。但任何組合必須包含 SYNCHRONIZE 訪問權(quán)。InheritHandle 參數(shù)表示子進(jìn)程是否可繼承該互斥對象句柄。 TMutex.Acquire 等效于WaitForSingleObject(mutexhandle, INFINITE) ,其實際上就是執(zhí)行 THandleObject.WaitFor(INFINITE)。 TMutex.Release 實際上就是執(zhí)行ReleaseMutex(mutexhandle)。 TMutex.Acquire 只能無限期等待一個互斥對象,要設(shè)置等待時間或等待多個互斥對象要使 用TMutex.WaitFor( ) 或TMutex.WaitForMultiple( )。 WaitFor( ) 是定義在TMutex 的父類ThandleObject 中的虛函數(shù),聲明如下: function WaitFor(Timeout: LongWord): TWaitResult; virtual; 其中返回值枚舉型TWaitResult 可以指示操作結(jié)果,wrSignaled 代表信號已set , wrTimeOut 代表超時且信號未set ,wrAbandoned 代表超時前事件對象被銷毀,wrError 代 表等待時出錯。 WaitForMultiple( ) 是定義在TMutex 的父類ThandleObject 中的類函數(shù),聲明如下: class function WaitForMultiple(const HandleObjs: THandleObjectArray; Timeout: LongWord; AAll: Boolean; out SignaledObj: THandleObject; UseCOMWait: Boolean = False; Len: Integer = 0): TWaitResult; 其中HandleObjs 參數(shù)是包含了要等待的一系列事件對象的數(shù)組,AAll 參數(shù)設(shè)為True 時, 當(dāng)所有事件對象都進(jìn)入發(fā)信號狀態(tài)后該函數(shù)調(diào)用才會完成,當(dāng)返回值為wrSignaled 且 AAll 參數(shù)設(shè)為False 時,第一個發(fā)信號的事件對象會被傳給SignaledObj 參數(shù),Len 參數(shù) 設(shè)置監(jiān)視事件對象的數(shù)量。 注意:WaitFor( ) 和WaitForMultiple( ) 均定義在ThandleObject 類中,而ThandleObject 類 是TMutex 、TSemaphore 、TEvent 類的父類,所以在描述WaitFor( ) 和WaitForMultiple( ) 時使用的是事件對象而非互斥對象或信號量對象。 3. Semaphore 信號量 另一種使線程同步的技術(shù)是使用信號量對象。它是在互斥的基礎(chǔ)上建立的,它與互斥相似, 但它可以計數(shù)。信號量增加了資源計數(shù)的功能,預(yù)定數(shù)目的線程允許同時進(jìn)入要同步的代碼。 8 例如可以允許一個給定資源同時被三個線程訪問。其實互斥就是最大計數(shù)為1 的信號量。 信號量的使用和互斥差不多。 (1). 使用CreateSemaphore( ) API 函數(shù) 可以用CreateSemaphore( ) 來創(chuàng)建一個信號量對象,其聲明如下: function CreateSemaphore(lpSemaphoreAttributes: PSecurityAttributes; lInitialCount, lMaximumCount: Longint; lpName: PWideChar): THandle; stdcall; 和CreateMutex( ) 函數(shù)一樣, CreateSemaphore( ) 的第一個參數(shù)也是一個指向 TSecurityAttributes 記錄的指針,此參數(shù)的缺省值可以設(shè)為nil 。 lInitialCount 參數(shù)用來指定一個信號量的初始計數(shù)值,這個值必須在0 和lMaximumCount 之間。此參數(shù)大于0 ,就表示信號量處于發(fā)信號狀態(tài)。參數(shù)lMaximumCount 指定計數(shù)值 的最大值。如果這個信號量代表某種資源,那么這個值代表可用資源總數(shù)。 參數(shù)lpName 用于給出信號量對象的名稱,它類似于CreateMutex( ) 函數(shù)的lpName 參數(shù)。 在程序中使用WaitForSingleObject( ) 來防止其他線程進(jìn)入同步區(qū)域的代碼。當(dāng)調(diào)用 WaitForSingleObject( ) 函數(shù)( 或其他WaitFor 函數(shù)) 時, 此計數(shù)值就減1 。當(dāng)調(diào)用 ReleaseSemaphore( ) 時,此計數(shù)值加1 ,此時同步區(qū)域代碼可以被其它線程訪問。其聲明 如下: function ReleaseSemaphore(hSemaphore: THandle; lReleaseCount: Longint; lpPreviousCount: Pointer): BOOL; stdcall; 其中hSemaphore 參數(shù)是創(chuàng)建的信號量句柄,lReleaseCount 參數(shù)是釋放時要增加的信號量 計數(shù),lpPreviousCount 參數(shù)是通過該指針參數(shù)來獲得釋放前的信號量計數(shù),如果不用設(shè)為 nil 。 當(dāng)使用完信號量時,應(yīng)當(dāng)調(diào)用CloseHandle( ) 來關(guān)閉它。 注意:一般的同步使用互斥,是因為其有一個特別之處,當(dāng)一個持有互斥的線程DOWN 掉 的時候,互斥可以自動讓其它等待這個對象的線程接受,而其它的內(nèi)核對象則不具體這個功 能。之所以要使用信號量則是因為其可以提供一個活動線程的上限,即lMaximumCount 參 數(shù),這才是它的真正有用之處。 例: var Form1 : TForm1; HSem : THandle = 0;//定義一個信號量 implementation var tick : Integer = 0; procedure TMyThread.Execute; var WaitReturn : DWord ; begin WaitReturn := WaitForSingleObject(HSem, INFINITE);//使用信號量對象,信號量減1 9 Form1.Edit1.Text := IntToStr(tick); Inc(tick); Sleep(10); ReleaseSemaphore(HSem, 1, Nil);//釋放信號量對象,信號量加1 end; … procedure TForm1.FormCreate(Sender: TObject); begin HSem := CreateSemaphore(Nil, 1, 1, Nil);//創(chuàng)建信號量對象 end; procedure TForm1.FormDestroy(Sender: TObject); begin CloseHandle(HSem);//銷毀信號量 end; procedure TForm1.Button1Click(Sender: TObject); var index : Integer; begin for index := 0 to 10 do TMyThread.Create; end; (2). 使用TSemaphore 類 TSemaphore 是在SyncObjs 單元中定義的類,其是ThandleObject 類的子類,要使用它需 要先uses SyncObjs 。它對上面的API 函數(shù)進(jìn)行了封裝,簡化并方便了在Delphi 中的使 用。 其有三個版本的構(gòu)造器,簡單執(zhí)行TSemaphore.Create 就可實例化一個對象: constructor Create(UseCOMWait: Boolean = False); overload; constructor Create(SemaphoreAttributes: PSecurityAttributes; AInitialCount: Integer; AMaximumCount: Integer; const Name: string; UseCOMWait: Boolean = False); overload; constructor Create(DesiredAccess: LongWord; InheritHandle: Boolean; const Name: string; UseCOMWait: Boolean = False); overload; 參數(shù)參見上面介紹。 TSemaphore.Acquire 等效于WaitForSingleObject(semaphorehandle, INFINITE) ,其實際上就 是執(zhí)行THandleObject.WaitFor(INFINITE)。或者使用WaitFor( ) 和WaitForMultiple( ) 函數(shù), 這兩個函數(shù)可以設(shè)置等待的時間或等待多個事件對象。 TSemaphore.Release 有兩個版本,聲明如下: procedure Release; override; overload; function Release(AReleaseCount: Integer): Integer; overload; reintroduce; 第一個版本實際執(zhí)行ReleaseSemaphore(FHandle, 1, nil) 第二個版本AReleaseCount 參數(shù)表示釋放時增加的信號量計數(shù)值,返回值是釋放前的信號 量計數(shù)值。實際執(zhí)行ReleaseSemaphore(FHandle, AReleaseCount, @Result),其中@Result 是 10 指向Release 函數(shù)返回值Integer 類型的指針。如果要指定增加計數(shù)值應(yīng)使用第二個版本。 4. Event 事件 事件( Event )與Delphi 中的事件有所不同。從本質(zhì)上說,Event 其實相當(dāng)于一個全局的布 爾變量。它有兩個賦值操作: SetEvent 和ResetEvent ,相當(dāng)于把它設(shè)置為True 或False 。 而檢查它的值是通過WaitForSingleObject( ) (或其它WaitFor 函數(shù))操作進(jìn)行。SetEvent 和 ResetEvent 操作是原語操作,所以Event 可以實現(xiàn)一般布爾變量不能實現(xiàn)的在多線程中的 應(yīng)用。 當(dāng)Event 從Reset 狀態(tài)向Set 狀態(tài)轉(zhuǎn)換時,喚醒其它掛起的線程,這就是它為什么叫 Event 的原因。所謂“事件”就是指“狀態(tài)的轉(zhuǎn)換”。通過Event 可以在線程間傳遞這種“狀 態(tài)轉(zhuǎn)換”信息。所以其本質(zhì)是用來通知某事已經(jīng)發(fā)生的信號,在這里可用來表示共享資源已 經(jīng)在使用或已經(jīng)使用完的信號。 (1). 使用CreateEvent( ) API 函數(shù) 使用CreateEvent( ) 創(chuàng)建一個事件,聲明如下: function CreateEvent(lpEventAttributes: PSecurityAttributes; bManualReset, bInitialState: BOOL; lpName: PWideChar): THandle; stdcall; 其中bManualReset 參數(shù)代表創(chuàng)建的Event 是自動復(fù)位還是人工復(fù)位,如果設(shè)為True 表示 人工復(fù)位,一旦該Event 被設(shè)置為有信號,則它一直會等到手動執(zhí)行ResetEvent( ) 時才會 變?yōu)闊o信號,設(shè)為False 表示自動復(fù)位,Event 被設(shè)置為有信號時,則當(dāng)有一個線程執(zhí)行 WaitForSingleObject( ) 時該Event 就會自動復(fù)位,變成無信號。bInitialState 參數(shù)代表事件 的初始狀態(tài),設(shè)為True,事件創(chuàng)建后為有信號,設(shè)為False 則為無信號。 不同于互斥或信號量,Event 不使用Release 相關(guān)函數(shù)設(shè)置相關(guān)對象進(jìn)入發(fā)信號狀態(tài),而使 用SetEvent( ) 函數(shù),當(dāng)線程執(zhí)行完同步代碼要從同步區(qū)域中離開時應(yīng)執(zhí)行該函數(shù),聲明如 下: function SetEvent(hEvent: THandle): BOOL; stdcall; 當(dāng)事件創(chuàng)建為人工復(fù)位時,在線程進(jìn)入同步區(qū)域執(zhí)行同步代碼前應(yīng)執(zhí)行ResetEvent( ) 函數(shù), 將Event 設(shè)為無信號。聲明如下: function ResetEvent(hEvent: THandle): BOOL; stdcall; PulseEvent( ) 是一個比較有意思的方法,正如名字,它使一個Event 對象的狀態(tài)發(fā)生一次 脈沖變化,將無信號設(shè)為有信號,喚醒等待的線程,再設(shè)為無信號,而整個操作是原子的。 對自動復(fù)位的Event 對象,它僅喚醒第一個等到該事件的線程(如果有的話),而對于人工復(fù) 位的Event 對象,它喚醒所有等待的線程。聲明如下: function PulseEvent(hEvent: THandle): BOOL; stdcall; 當(dāng)使用完事件時,應(yīng)當(dāng)調(diào)用CloseHandle( ) 來關(guān)閉它。 (2). 使用TEvent 類 TEvent 是在SyncObjs 單元中定義的類,其是ThandleObject 類的子類,要使用它需要先 uses SyncObjs 。它對上面的API 函數(shù)進(jìn)行了封裝,簡化并方便了在Delphi 中的使用。 TEvent 若在多線程環(huán)境中可用于與其它線程同步;若在單線程環(huán)境中可用于調(diào)整響應(yīng)不同 異步事件(如系統(tǒng)消息或用戶動作)的代碼段。構(gòu)造函數(shù)如下: constructor Create(EventAttributes: PSecurityAttributes; ManualReset: Boolean; InitialState: Boolean; const Name: string; UseCOMWait: Boolean = False); overload; 11 constructor Create(UseCOMWait: Boolean = False); overload; ManualReset 參數(shù)為是否手工復(fù)位,InitialState 參數(shù)為初始狀態(tài)。 TEvent.SetEvent( ) 和TEvent.ResetEvent( ) 均無參數(shù)。 TEvent 類中沒有定義與PulseEvent 功能一樣的方法。 TEvent 類同樣可以使用WaitFor( ) 和WaitForMultiple( ) 函數(shù)。 但要注意的是,TEvent 類并沒有實現(xiàn)Acquire 函數(shù),該函數(shù)是定義在TSynchroObject 類 中僅作為接口、沒有執(zhí)行代碼的虛函數(shù)。TSynchroObject 是ThandleObject 類的父類。其實 自己實現(xiàn)Acquire 函數(shù)也不難,它實際上是執(zhí)行THandleObject.WaitFor(INFINITE) 函數(shù), 仿照上面的TMutex 類寫就可以。 另外,Delphi 中定義了一個更簡單的事件類,TSimpleEvent 類,但從源代碼上看,該類僅 有TSimpleEvent = class(TEvent); 一句,并未定義任何屬于TSimpleEvent 的成員。估計是 作為向后兼容而存在。 5. Global Atom 全局原子 Windows 系統(tǒng)中,為了實現(xiàn)信息共享,系統(tǒng)維護了一張全局原子表( Global Atom Table ), 用于保存字符串與之對應(yīng)的標(biāo)志符(原子)的組合,系統(tǒng)能保證其中的每個原子都是唯一的, 管理其引用計數(shù),并且當(dāng)該全局原子的引用計數(shù)為0 時,從內(nèi)存中清除。應(yīng)用程序在原子表 中可以放置字符串,并接收一個16 位整數(shù)值(叫做原子,即Atom ),它可以用來提取該字 符串。放在原子表中的字符串叫做原子的名字。系統(tǒng)提供了許多原子表。每個表有不同的目 的。例如,動態(tài)數(shù)據(jù)交換( DDE )應(yīng)用程序使用全局原子表與其他應(yīng)用程序共享項目名稱和 主題名稱字符串,不傳遞實際的字符串,一個DDE 應(yīng)用程序傳遞全局原子給它的父進(jìn)程, 父進(jìn)程使用原子提取原子表中的字符串,這就是利用全局原子進(jìn)行進(jìn)程或線程間的數(shù)據(jù)交 換;使用全局原子也可防止多次啟動某個程序。 應(yīng)用程序可以使用本地原子表來有效地管理大量只用于程序內(nèi)部的字符串。這些字符串,以 及相關(guān)聯(lián)的原子,只對創(chuàng)建該原子表的應(yīng)用程序可用。一個在許多數(shù)據(jù)結(jié)構(gòu)中需要相同字符 串的應(yīng)用程序,可以通過使用本地原子表來減少內(nèi)存使用。程序可以把字符串放入原子表, 把相關(guān)的原子放入結(jié)構(gòu),而無需把字符串拷到每個結(jié)構(gòu)中。這樣,一個字符串在內(nèi)存中只出 現(xiàn)一次,但可以在程序中多次使用。應(yīng)用程序也可以使用本地原子表來快速搜索特定的字符 串。要實現(xiàn)這樣的搜索,程序只需把要搜索的字符串放入原子表中,然后把結(jié)果原子與相關(guān) 數(shù)據(jù)結(jié)構(gòu)中的原子相比較。通常情況下,比較原子要比比較字符串要快得多。原子表是用哈 希表實現(xiàn)的。默認(rèn)時,一個本地原子表使用37 個bucket 的哈希表。不過,你可以通過調(diào) 用InitAtomTable 函數(shù)來改變bucket 數(shù)量。如果程序準(zhǔn)備調(diào)用InitAtomTable ,那它必須 在調(diào)用任何其他原子管理函數(shù)前調(diào)用它。這里只簡單介紹本地原子表。它有多個相關(guān)的函數(shù), function InitAtomTable(nSize: DWORD): BOOL; stdcall; function DeleteAtom(nAtom: ATOM): ATOM; stdcall; function AddAtom(lpString: PWideChar): ATOM; stdcall; function FindAtom(lpString: PWideChar): ATOM; stdcall; function GetAtomName(nAtom: ATOM; lpBuffer: PWideChar; nSize: Integer): UINT; stdcall; 以下介紹全局原子表相關(guān)函數(shù)。 function GlobalAddAtom(lpString: PWideChar): ATOM; stdcall; 增加一個字符串到全局原子表中,并返回一個唯一標(biāo)識值。 lpString 參數(shù)為要添加到全局原子表中的字符串。 如果成功返回新增加的全局原子,失敗則返回0 。ATOM 類型等于Word 類型。 12 function GlobalDeleteAtom(nAtom: ATOM): ATOM; stdcall; 減少對指定全局原子的引用計數(shù),引用計數(shù)減1 ,如果引用計數(shù)為零,系統(tǒng)會在全局原子 表中刪除此原子。 此函數(shù)一直返回0 。 只要全局原子的引用計數(shù)大于0 ,其原子名稱將保留在全局原子表中,即使把它放入表中 的應(yīng)用程序終結(jié)了。一個本地的原子表在應(yīng)用程序終結(jié)時被銷毀,而不管其中原子的引用計 數(shù)是多少。 function GlobalFindAtom(lpString: PWideChar): ATOM; stdcall; 在全局原子表中查找是否存在指定字符串。 lpString 參數(shù)為要查找的字符串。 如果在全局原子表中存在要查找的字符串,則返回此字符串對應(yīng)的原子,沒有找到則返回0。 function GlobalGetAtomName(nAtom: ATOM; lpBuffer: PWideChar; nSize: Integer): UINT; stdcall; 返回指定原子所對應(yīng)的字符串。 nAtom 參數(shù)為指定查找的原子,lpBuffer 參數(shù)為要存放字符串的緩沖區(qū),nSize 參數(shù)為緩沖 區(qū)大小。 若操作成功返回緩沖區(qū)接受長度,若失敗返回0 。UINT 類型等于LongWord 類型。 例: //在程序的program 文件中 ... if GlobalFindAtom(iAtom) = 0 then begin Application.Initialize; Application.CreateForm(TForm1, Form1); Application.Run; end else MessageBox(0, '已經(jīng)有一個程序在運行', ' ', mb_OK); ... 6. Synchronize 同步 Synchronize( ) 是定義在TThread 類中的函數(shù),它可以讓要執(zhí)行的代碼實現(xiàn)線程同步,但這 種同步其實是偽同步,其原理是將子線程要執(zhí)行的代碼通過消息傳遞給主線程,由主線程來 執(zhí)行,主線程將代碼放在一個隱蔽的窗口里運行,而子線程會等待主線程將執(zhí)行結(jié)果發(fā)給它, 這樣的話,這段代碼就不是子線程代碼,而是一般的主線程代碼。Synchronize( ) 只是將該 線程的代碼放到主線程中運行,并非實際意義的線程同步。RAD Studio VCL Reference 中也 描述為:Executes a method call within the main thread,Synchronize causes the call specified by AMethod(參數(shù)) to be executed using the main thread,,thereby avoiding multi-thread conflicts。 這里有一個問題,如果Synchronize( ) 執(zhí)行的代碼很繁忙,例如執(zhí)行的代碼運算過于復(fù)雜、 龐大或者從數(shù)據(jù)庫中取出大量數(shù)據(jù),數(shù)據(jù)庫不會立即返回數(shù)據(jù)時或者使用ADO 組件連接 數(shù)據(jù)庫,而這時數(shù)據(jù)庫無法連接,ADO 組件需要超時才會終止運行,這些都會導(dǎo)致主窗口 會阻塞掉,看似死機一般。因此,通常對用戶界面類VCL 組件的訪問才使用Synchronize( ) 13 函數(shù),一般用戶界面類VCL 組件都由主線程創(chuàng)建、存在于主窗口中,而且對VCL 組件的 訪問或修改的執(zhí)行效率都比較高,不會過多的影響性能。絕對不能在主線程中執(zhí)行 Synchronize( ) 函數(shù),這會導(dǎo)致無限循環(huán)。 Synchronize( ) 函數(shù)一般在線程的Execute 函數(shù)中調(diào)用。其有四個版本,兩個是類函數(shù),兩 個是靜態(tài)函數(shù),聲明如下: class procedure Synchronize(AThread: TThread; AMethod: TThreadMethod); overload; class procedure Synchronize(AThread: TThread; AThreadProc: TThreadProcedure); overload; procedure Synchronize(AMethod: TThreadMethod); overload; procedure Synchronize(AThreadProc: TThreadProcedure); overload; AThread 參數(shù)是當(dāng)前線程,TThreadMethod 是對象的函數(shù)指針類型,TThreadProcedure 是匿 名函數(shù)類型。 注意:Synchronize( ) 的AMethod 或AThreadProc 參數(shù)必須是一個無參數(shù)的procedure , 故在此procedure 中無法傳遞參數(shù)值,通常的解決方法是在線程類中增加額外的成員,用其 代替參數(shù)來傳遞信息。 例: type TMyThread = class(TThread) str : string;//額外的域,代替參數(shù)將字符串寫入Memo ... procedure TMyThread.WriteMemo; begin Memo.Lines.Add(str); end; ... procedure TMyThread.Execute; begin str := 'Hello'; synchronize(WriteMemo); end; |
|