临界区的实现原理

原创
小哥 2年前 (2023-05-23) 阅读数 42 #大杂烩

转自:http://my.oschina.net/myspaceNUAA/blog/81244

关键部分概述:

用于具有多个线程的互斥锁访问。如果多个线程尝试同时访问关键部分,则在一个线程进入关键部分后,其他尝试访问的线程将被挂起,直到进入关键部分的线程离开。释放关键部分后,其他线程可以继续抢占它,以实现对关键部分的互斥访问。(关键部分一般为短代码段)
在WINDOWS在中,关键部分是应用层同步对象,而不是内核对象。并且关键部分被旋转抢占

临界区API:

关键部分的初始化和删除:

InitializeCriticalSection() DeleteCriticalSection()

关键部分的两个操作基元:

EnterCriticalSection() LeaveCriticalSection()

关键部分的数据结构:

typedef struct _RTL_CRITICAL_SECTION { PRTL_CRITICAL_SECTION_DEBUG DebugInfo; // // The following three fields control entering and exiting the critical // section for the resource //

LONG LockCount;
LONG RecursionCount;
HANDLE OwningThread;        // from the threads ClientId->UniqueThread
HANDLE LockSemaphore;
ULONG\_PTR SpinCount;        // force size on 64-bit systems when packed

} RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION;

_RTL_CRITICAL_SECTION_DEBUG数据结构

typedef struct _RTL_CRITICAL_SECTION_DEBUG { WORD Type; WORD CreatorBackTraceIndex; struct _RTL_CRITICAL_SECTION CriticalSection; LIST_ENTRY ProcessLocksList; DWORD EntryCount; DWORD ContentionCount; DWORD Flags; WORD CreatorBackTraceIndexHigh; WORD SpareWORD ; } RTL_CRITICAL_SECTION_DEBUG, PRTL_CRITICAL_SECTION_DEBUG, RTL_RESOURCE_DEBUG, *PRTL_RESOURCE_DEBUG;

(代码来自VS2005 WINNT.h)

_RTL_CRITICAL_SECTION 各字段说明:

DebugInfo:指向要调试的数据,此结构的类型为RTL_CRITICAL_SECTION_DEBUG LockCount: 初始值-1如果结果大于或等于0,表示关键部分已被线程占用。 OwningThread: 当前拥有关键部分的线程 RecursionCount:所有者线程连续进入关键部分的次数 LockSemaphore: 内核对象句柄,用于通知操作系统关键部分当前处于空闲状态,用于唤醒因等待关键部分而挂起的线程

SpinCount:MSDN按如下方式解释此字段:

"On multiprocessor systems, if the critical section is unavailable, the calling thread will spin dwSpinCount times before performing a wait operation on a semaphore associated with the critical section. If the critical section becomes free during the spin operation, the calling thread avoids the wait operation."

在多处理器系统中,如果关键部分被占用,线程将旋转SpinCount第二次是获取关键部分,而不是通过阻塞和等待来获取关键部分。如果关键部分的空间正在旋转过程中,可以直接进入关键部分以减少等待时间(如果进入等待状态,则需要将用户状态切换到内核状态,成本很高)。主要思想是提高效率。下面,我们将分析什么是自旋。

临界区API实现过程

InitialzeCriticalSection

在初始化过程中,将对其进行测试CPU数量,如果CPU数量为1忙着等待是没有意义的。是SpinCount=0,
若CPU数量大于1,则设置SpinCount进入临界区时,将采用主动进入策略。

EnterCriticalSection

  1. 如果关键部分尚未被占用,则会更新关键部分的数据结构,表示调用线程已获得访问关键部分的权限,然后返回。
  2. 如果线程已经获得访问权限,并且EnterCriticalSection,更新线程获得访问权限的次数(即连续Enter次数)。
  3. 如果关键部分已被其他线程占用,则当前线程 通过SpinCount控制繁忙等待时间SpinCount已经等于0如果没有获取到关键段对象,函数会直接通过关键段对象内部的事件对象等待(等待和唤醒涉及用户模式和内核模式的切换,这不是最优解,最好通过旋转进入关键段)。忙于等待通过以下方式实现LockCount实现原子读取和写入操作。

您可以在以下网站找到具体的汇编代码:来源:( http://blog.csdn.net/joeleechj/article/details/7081124)

RtlLeaveCriticalSection

  1. _RTL_CRITICAL_SECTION在数据结构中设置相关标志位 ,比如RecursionCount--,如果为0,表示没有线程占用关键部分
  2. 将当前线程句柄设置为0,表示当前临界段处于信号状态,可以得到
  3. 如果有其他线程在等待,请唤醒等待线程

CRITICAL_SECTION和CRITICAL_SECTION_DEBUG两者之间的关系
http://msdn.microsoft.com/zh-cn/magazine/cc164040(en-us).aspx)

通过这种数据结构可以发现,在这个过程中DEBUG信息通过链表连接。在已知的关键部分中
情况下, 通过链表数据结构,可以访问数据的关键部分对象。

自旋:


用于关键部分(EnterCriticalSection)操作是主动进入关键部分。意思是,当没有办法进入时,不断主动尝试进入,直到进入为止。这种活动的进入方法称为旋转(也称为忙于等待)。
被动模式:如果无法获取,则进入等待队列。当释放待获取的对象时,系统会唤醒等待线程,称为被动模式。

小结:

1.进入灵区和离开关键部分是配对操作。进入关键部分必须离开关键部分,否则受关键部分保护的共享资源将永远不会释放。

2.使用关键部分时,关键部分使用的代码要短,以减少其他线程的等待时间,提高程序性能。

3.关键部分的同步速度非常快,但只能用于同步进程中的线程,不能用于同步多个进程中的线程。

  1. 关键部分是用户模式下的对象,不是内核对象,因此使用时无需在用户模式和内核模式之间切换,效率明显高于其他互斥内核对象。

参考:

http://msdn.microsoft.com/zh-cn/magazine/cc164040(en-us).aspx


版权声明

所有资源都来源于爬虫采集,如有侵权请联系我们,我们将立即删除

热门