乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      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ū)。運用這個函數(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; 

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

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多