小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

delphi 線程同步的基本方法

 ZLM_圖書館 2014-02-11

上次跟大家分享了線程的標(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ū)。運(yùn)用這個函數(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)用程序共享。互斥的效果非常類似于臨界區(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)行同步的,但它們間有以下一點(diǎ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ù)運(yùn)行。

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)機(jī)文檔。

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)維護(hù)了一張全局原子表( 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)有一個程序在運(yùn)行', ' ', mb_OK);

...

6. Synchronize 同步

Synchronize( ) 是定義在TThread 類中的函數(shù),它可以讓要執(zhí)行的代碼實現(xiàn)線程同步,但這

種同步其實是偽同步,其原理是將子線程要執(zhí)行的代碼通過消息傳遞給主線程,由主線程來

執(zhí)行,主線程將代碼放在一個隱蔽的窗口里運(yùn)行,而子線程會等待主線程將執(zhí)行結(jié)果發(fā)給它,

這樣的話,這段代碼就不是子線程代碼,而是一般的主線程代碼。Synchronize( ) 只是將該

線程的代碼放到主線程中運(yùn)行,并非實際意義的線程同步。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í)行的代碼運(yùn)算過于復(fù)雜、

龐大或者從數(shù)據(jù)庫中取出大量數(shù)據(jù),數(shù)據(jù)庫不會立即返回數(shù)據(jù)時或者使用ADO 組件連接

數(shù)據(jù)庫,而這時數(shù)據(jù)庫無法連接,ADO 組件需要超時才會終止運(yùn)行,這些都會導(dǎo)致主窗口

會阻塞掉,看似死機(jī)一般。因此,通常對用戶界面類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ū)。運(yùn)用這個函數(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)行同步的,但它們間有以下一點(diǎ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ù)運(yùn)行。
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)機(jī)文檔。
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)維護(hù)了一張全局原子表( 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)有一個程序在運(yùn)行', ' ', mb_OK);
...
6. Synchronize 同步
Synchronize( ) 是定義在TThread 類中的函數(shù),它可以讓要執(zhí)行的代碼實現(xiàn)線程同步,但這
種同步其實是偽同步,其原理是將子線程要執(zhí)行的代碼通過消息傳遞給主線程,由主線程來
執(zhí)行,主線程將代碼放在一個隱蔽的窗口里運(yùn)行,而子線程會等待主線程將執(zhí)行結(jié)果發(fā)給它,
這樣的話,這段代碼就不是子線程代碼,而是一般的主線程代碼。Synchronize( ) 只是將該
線程的代碼放到主線程中運(yùn)行,并非實際意義的線程同步。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í)行的代碼運(yùn)算過于復(fù)雜、
龐大或者從數(shù)據(jù)庫中取出大量數(shù)據(jù),數(shù)據(jù)庫不會立即返回數(shù)據(jù)時或者使用ADO 組件連接
數(shù)據(jù)庫,而這時數(shù)據(jù)庫無法連接,ADO 組件需要超時才會終止運(yùn)行,這些都會導(dǎo)致主窗口
會阻塞掉,看似死機(jī)一般。因此,通常對用戶界面類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; 

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多