臨界區段

在同步的程式設計中,臨界區段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頁面存檔備份,存於網際網路檔案館