临界区段

在同步的程式设计中,临界区段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页面存档备份,存于互联网档案馆