關於 TRTLCriticalSection

shely發表於2009-10-30

臨界區是一種最直接的執行緒同步方式。所謂臨界區,就是一次只能由一個執行緒來執行的一段程式碼。如果把初始化陣列的程式碼放在臨界區內,另一個執行緒在第一個執行緒處理完之前是不會被執行的。在使用臨界區之前,必須使用InitializeCriticalSection()過程來初始化它。

其宣告如下:

procedure InitializeCriticalSection(var lpCriticalSection: TRTLCriticalSection); stdcall;

lpCriticalSection引數是一個TRTLCriticalSection型別的記錄,並且是變參。至於TRTLCriticalSection是如何定義的,這並不重要,因為很少需要檢視這個記錄中的具體內容。只需要在lpCriticalSection中傳遞未初始化的記錄,InitializeCriticalSection()過程就會填充這個記錄。

注意Microsoft故意隱瞞了TRTLCriticalSection的細節。因為,其內容在不同的硬體平臺上是不同的。在基於Intel的平臺上,TRTLCriticalSection包含一個計數器、一個指示當前執行緒控制程式碼的域和一個系統事件的控制程式碼。在Alpha平臺上,計數器被替換為一種Alpha-CPU 資料結構,稱為spinlock。

在記錄被填充後,我們就可以開始建立臨界區了。這時我們需要用EnterCriticalSection()和LeaveCriticalSection()來封裝程式碼塊。這兩個過程的宣告如下:

procedure EnterCriticalSection(var lpCriticalSection: TRTLCriticalSection); stdcall;

procedure LeaveCriticalSection(var lpCriticalSection:TRTLCriticalSection);

stdcall;

正如你所想的,引數lpCriticalSection就是由InitializeCriticalSection()填充的記錄。

當你不需要TRTLCriticalSection記錄時,應當呼叫DeleteCriticalSection()過程,下面是它的宣告:

procedure DeleteCriticalSection(var lpCriticalSection:TRTLCriticalSection);stdcall

看看u2m(UpToMe)的例子:

在第一個執行緒呼叫了EnterCriticalSection()之後,所有別的執行緒就不能再進入程式碼塊。下一個執行緒要等第一個執行緒呼叫LeaveCriticalSection()後才能被喚醒。

當有多個執行緒的時候,經常需要去同步這些執行緒以訪問同一個資料或資源。例如,假設有一個程

序,其中一個執行緒用於把檔案讀到記憶體,而另一個執行緒用於統計檔案中的字元數。當然,在把整個文

件調入記憶體之前,統計它的計數是沒有意義的。但是,由於每個操作都有自己的執行緒,作業系統會把

兩個執行緒當作是互不相干的任務分別執行,這樣就可能在沒有把整個檔案裝入記憶體時統計字數。為解

決此問題,你必須使兩個執行緒同步工作。

存在一些執行緒同步地址的問題,Win32提供了許多執行緒同步的方式。在本節你將看到使用臨界區、

互斥、訊號量和事件來解決執行緒同步的問題。

1. 臨界區

臨界區是一種最直接的執行緒同步方式。所謂臨界區,就是一次只能由

一個執行緒來執行的一段程式碼。如果把初始化陣列的程式碼放在臨界區內,另

一個執行緒在第一個執行緒處理完之前是不會被執行的。

在使用臨界區之前,必須使用InitializeCriticalSection()過程來初始化它。

其宣告如下:

procedure InitializeCriticalSection(var lpCriticalSection:

TRLCriticalSection);stdcall;

lpCriticalSection引數是一個TRTLCriticalSection型別的記錄,並且是變參。至於TRTLCriticalSection

是如何定義的,這並不重要,因為很少需要檢視這個記錄中的具體內容。只需要在lpCriticalSection中傳

遞未初始化的記錄,InitializeCriticalSection()過程就會填充這個記錄。

注意Microsoft故意隱瞞了TRTLCriticalSection的細節。因為,其內容在不同的硬體平臺上是

不同的。在基於Intel的平臺上,TRTLCriticalSection包含一個計數器、一個指示當前執行緒控制程式碼

的域和一個系統事件的控制程式碼。在Alpha平臺上,計數器被替換為一種Alpha-CPU 資料結構,稱

為spinlock。在記錄被填充後,我們就可以開始建立臨界區了。這時我們需要用EnterCriticalSection()和

LeaveCriticalSection()來封裝程式碼塊。這兩個過程的宣告如下:

procedure EnterCriticalSection(var lpCriticalSection:TRRLCriticalSection);stdcall;

procedure LeaveCriticalSection(var lpCriticalSection:TRRLCriticalSection);stdcall;

正如你所想的,引數lpCriticalSection就是由InitializeCriticalSection()填充的記錄。

當你不需要TRTLCriticalSection記錄時,應當呼叫DeleteCriticalSection()過程,下面是它的宣告:

procedure DeleteCriticalSection(var lpCriticalSection: TRTLCriticalSection); stdcall;

2. 互斥

互斥非常類似於臨界區,除了兩個關鍵的區別:首先,互斥可用於跨

程式的執行緒同步。其次,互斥能被賦予一個字串名字,並且通過引用此

名字建立現有互斥物件的附加控制程式碼。

提示臨界區與事件物件(比如互斥物件)的最大的區別是在效能上。臨

界區在沒有執行緒衝突時,要用1 0 ~ 1 5個時間片,而事件物件由於涉及到

系統核心要用400~600個時間片。

可以呼叫函式CreateMutex ( )來建立一個互斥量。下面是函式的宣告:

function CreateMutext(lpMutextAtrribes:PSecurityAttributtes;

bInitalOwner:BOOL; lpName:PChar):THandle; stdcall;

lpMutexAttributes引數為一個指向TSecurityAttributtes記錄的指標。此引數通常設為0,表示預設的

安全屬性。bInitalOwner參數列示建立互斥物件的執行緒是否要成為此互斥物件的擁有者。當此引數為False時,

表示互斥物件沒有擁有者。

lpName引數指定互斥物件的名稱。設為nil表示無命名,如果引數不是設為nil,函式會搜尋是否有

同名的互斥物件存在。如果有,函式就會返回同名互斥物件的控制程式碼。否則,就新建立一個互斥物件並

返回其控制程式碼。

當使用完互斥物件時,應當呼叫CloseHandle()來關閉它。

在程式中使用WaitForSingleObject()來防止其他執行緒進入同步區域的程式碼。此函式聲

明如下:

function WaitForSingleObject(hHandle: THandle; dwMilliseconds: DWORD): DWORD; stdcall;

這個函式可以使當前執行緒在dwMilliseconds指定的時間內睡眠,直到hHandle引數指定的物件進入

發訊號狀態為止。一個互斥物件不再被執行緒擁有時,它就進入發訊號狀態。當一個程式要終止時,它

就進入發訊號狀態。dwMilliseconds引數可以設為0,這意味著只檢查hHandle引數指定的物件是否處於

發訊號狀態,而後立即返回。dwMilliseconds引數設為INFINITE,表示如果訊號不出現將一直等下去。

這個函式的返回值如下

WaitFor SingleObject()函式使用的返回值

返回值 含義

WAIT_ABANDONED 指定的物件是互斥物件,並且擁有這個互斥物件的執行緒在沒有釋放此物件之

前就已終止。此時就稱互斥物件被拋棄。這種情況下,這個互斥物件歸當前線

程所有,並把它設為非發訊號狀態

WAIT_OBJECT_0 指定的物件處於發訊號狀態

WAIT_TIMEOUT等待的時間已過,物件仍然是非發訊號狀態

再次宣告,當一個互斥物件不再被一個執行緒所擁有,它就處於發訊號狀態。此時首先呼叫WaitForSingleObject()函式

的執行緒就成為該互斥物件的擁有者,此互斥物件設為不發訊號狀態。當執行緒呼叫

ReleaseMutex()函式並傳遞一個互斥物件的控制程式碼作為引數時,這種擁有關係就被解除,互斥物件重新

進入發訊號狀態。

注意除WaitForSingleObject()函式外,你還可以使用WaitForMultipleObject()和MsgWaitForMultipleObject()函式,

它們可以等待幾個物件變為發訊號狀態。這兩個函式的詳細情況請看

Win32 API聯機文件。

3. 訊號量

另一種使執行緒同步的技術是使用訊號量物件。它是在互斥的基礎上建立的,但訊號量增加了資源

計數的功能,預定數目的執行緒允許同時進入要同步的程式碼。可以用CreateSemaphore()來建立一個訊號

量物件,其宣告如下:

function CreateSemaphore(lpSemaphoreAttributes: PSecurityAttributes;

lInitialCount, lMaximumCount: Longint; lpName: PChar): THandle; stdcall;

和CreateMutex()函式一樣,CreateSemaphore()的第一個引數也是一個指向TSecurityAttribute s記錄

的指標,此引數的預設值可以設為nil。

lInitialCount引數用來指定一個訊號量的初始計數值,這個值必須在0和lMaximumCount之間。此

引數大於0,就表示訊號量處於發訊號狀態。當呼叫WaitForSingleObject()函式(或其他函式)時,此計

數值就減1。當呼叫ReleaseSemaphore()時,此計數值加1。

引數lMaximumCount指定計數值的最大值。如果這個訊號量代表某種資源,那麼這個值代表可用

資源總數。

引數lpName用於給出訊號量物件的名稱,它類似於CreateMutex()函式的lpName引數。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/7299296/viewspace-617782/,如需轉載,請註明出處,否則將追究法律責任。