臨界區段

在同步的程式設計中,臨界區段Critical section)或稱為關鍵區段 ,指的是一個存取共享資源(例如:共享裝置或是共享記憶體)的程式片段,而這些共享資源又無法同時被多個執行緒存取的特性。[1]

當有執行緒進入臨界區段時,其他執行緒或是行程必須等待(例如:bounded waiting 等待法),有一些同步的機制必須在臨界區段的進入點與離開點實現,以確保這些共享資源是被互斥或的使用,例如:semaphore

只能被單一執行緒存取的裝置,例如:印表機

一個最簡單的實現方法就是當執行緒(Thread)進入臨界區段時,禁止改變處理器;在Single-Processor系統上,可以用「禁止中斷(CLI)」來完成,避免發生系統呼叫(System Call)導致的上下文交換(Context switching);當離開臨界區段時,處理器回復原先的狀態。

Windows作業系統的臨界區

Windows作業系統,CRITICAL_SECTION是一種同步對象類型,用於同一個行程內的多執行緒同步訪問資源。如果是跨行程同步,需要使用互斥鎖(mutex)。

臨界區對象首先需要初始化,通過呼叫作業系統API函式InitializeCriticalSection。各個執行緒呼叫函式 EnterCriticalSection, TryEnterCriticalSection, 或LeaveCriticalSection來使用臨界區。使用結束後或者重初始化臨界區之前,需要呼叫 DeleteCriticalSection 。

WINNT.H中定義的臨界區資料結構如下:

struct RTL_CRITICAL_SECTION
{
   PRTL_CRITICAL_SECTION_DEBUG DebugInfo;
   LONG LockCount;
   LONG RecursionCount;
   HANDLE OwningThread;
   HANDLE LockSemaphore;
   ULONG_PTR SpinCount;
};

結構的各域的解釋:

  • DebugInfo 此欄位為一個指標,指向系統分配的結構RTL_CRITICAL_SECTION_DEBUG。這一結構中包含更多極有價值的資訊,也定義於 WINNT.H 中。
  • LockCount 被初始化為數值 -1;此數值等於或大於 0 時,表示此臨界區被占用。當其不等於 -1 時,OwningThread 欄位包含了擁有此臨界區的執行緒 ID。此欄位與 (RecursionCount-1) 數值之間的差值表示有多少個其他執行緒在等待獲得該臨界區。
  • RecursionCount 初始值為0. 此欄位包含所有者執行緒已經獲得該臨界區的次數。如果該數值為零,下一個嘗試取得該臨界區的執行緒將會成功。
  • OwningThread 此欄位包含當前占用此臨界區的執行緒的執行緒識別碼(應為DWORD而不是HANDLE)。此執行緒 ID 與 GetCurrentThreadId 之類的 API 所返回的 ID 相同。
  • LockSemaphore 是一個自復位的Event核心對象控制代碼。首次發生執行緒申請該臨界區被阻止時,作業系統自動建立該控制代碼。應當呼叫 DeleteCriticalSection(它將發出一個呼叫該事件的 CloseHandle 呼叫,並在必要時釋放該除錯結構),否則將會發生資源洩漏。
  • SpinCount 僅用於多處理器系統。MSDN文件:「在多處理器系統中,如果該臨界區不可用,呼叫執行緒將在對與該臨界區相關的訊號執行等待操作之前,旋轉 dwSpinCount 次。如果該臨界區在旋轉操作期間變為可用,該呼叫執行緒就避免了等待操作。」自旋計數可以在多處理器電腦上提供更佳效能,其原因在於在一個迴圈中自旋通常要快於進入核心模式等待狀態。此欄位預設值為零,但可以用InitializeCriticalSectionAndSpinCount API 將其設定為一個不同值。

RTL_CRITICAL_SECTION_DEBUG結構如下:

struct _RTL_CRITICAL_SECTION_DEBUG
{
    WORD   Type;
    WORD   CreatorBackTraceIndex;
    RTL_CRITICAL_SECTION *CriticalSection;
    LIST_ENTRY ProcessLocksList;
    DWORD EntryCount;
    DWORD ContentionCount;
    DWORD Spare[ 2 ];
}

結構的各域的解釋:

  • Type 此欄位未使用,被初始化為數值 0。
  • CreatorBackTraceIndex 此欄位僅用於診斷情形中。在登錄檔項 HKLM\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\YourProgram 之下是 keyfield、GlobalFlag 和 StackTraceDatabaseSizeInMb 值。注意,只有在執行稍後說明的 Gflags 命令時才會顯示這些值。這些登錄檔值的設定正確時,CreatorBackTraceIndex 欄位將由堆疊跟蹤中所用的一個索引值填充。在 MSDN 中搜尋 GFlags 文件中的短語「create user mode stack trace database」和「enlarging the user-mode stack trace database」。
  • CriticalSection 指向與此結構相關的 RTL_CRITICAL_SECTION
  • ProcessLocksList: LIST_ENTRY 是用於表示雙向鏈結串列中節點的標準 Windows 資料結構。RTL_CRITICAL_SECTION_DEBUG 包含了鏈結串列的一部分,允許向前和向後遍歷該臨界區。Flink=NULL為表頭,Blink=NULL為表尾
  • EntryCount/ContentionCount 這些欄位在相同的時間、出於相同的原因被遞增。這是不能獲得臨界區而進入等待狀態的執行緒的數目。與 LockCount 和 RecursionCount 欄位不同,這些欄位永遠都不會遞減。
  • Spares 這兩個欄位未使用,甚至未被初始化。

參考資料

  1. ^ Raynal, Michel. Concurrent Programming: Algorithms, Principles, and Foundations. Springer Science & Business Media. 2012: 9. ISBN 978-3642320279. 

參見

外部連結

MSDN Library -- Critical section頁面存檔備份,存於網際網路檔案館