临界区段
此条目需要补充更多来源。 (2018年3月13日) |
在同步的程式设计中,临界区段(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 这两个字段未使用,甚至未被初始化。
参考资料
- ^ Raynal, Michel. Concurrent Programming: Algorithms, Principles, and Foundations. Springer Science & Business Media. 2012: 9. ISBN 978-3642320279.