临界区的实现原理
原创转自: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
- 如果关键部分尚未被占用,则会更新关键部分的数据结构,表示调用线程已获得访问关键部分的权限,然后返回。
- 如果线程已经获得访问权限,并且EnterCriticalSection,更新线程获得访问权限的次数(即连续Enter次数)。
- 如果关键部分已被其他线程占用,则当前线程 通过SpinCount控制繁忙等待时间SpinCount已经等于0如果没有获取到关键段对象,函数会直接通过关键段对象内部的事件对象等待(等待和唤醒涉及用户模式和内核模式的切换,这不是最优解,最好通过旋转进入关键段)。忙于等待通过以下方式实现LockCount实现原子读取和写入操作。
您可以在以下网站找到具体的汇编代码:来源:( http://blog.csdn.net/joeleechj/article/details/7081124)
RtlLeaveCriticalSection
- _RTL_CRITICAL_SECTION在数据结构中设置相关标志位 ,比如RecursionCount--,如果为0,表示没有线程占用关键部分
- 将当前线程句柄设置为0,表示当前临界段处于信号状态,可以得到
- 如果有其他线程在等待,请唤醒等待线程
CRITICAL_SECTION和CRITICAL_SECTION_DEBUG两者之间的关系
( http://msdn.microsoft.com/zh-cn/magazine/cc164040(en-us).aspx)
通过这种数据结构可以发现,在这个过程中DEBUG信息通过链表连接。在已知的关键部分中
情况下, 通过链表数据结构,可以访问数据的关键部分对象。
自旋:
用于关键部分(EnterCriticalSection)操作是主动进入关键部分。意思是,当没有办法进入时,不断主动尝试进入,直到进入为止。这种活动的进入方法称为旋转(也称为忙于等待)。
被动模式:如果无法获取,则进入等待队列。当释放待获取的对象时,系统会唤醒等待线程,称为被动模式。
小结:
1.进入灵区和离开关键部分是配对操作。进入关键部分必须离开关键部分,否则受关键部分保护的共享资源将永远不会释放。
2.使用关键部分时,关键部分使用的代码要短,以减少其他线程的等待时间,提高程序性能。
3.关键部分的同步速度非常快,但只能用于同步进程中的线程,不能用于同步多个进程中的线程。
- 关键部分是用户模式下的对象,不是内核对象,因此使用时无需在用户模式和内核模式之间切换,效率明显高于其他互斥内核对象。
参考:
http://msdn.microsoft.com/zh-cn/magazine/cc164040(en-us).aspx
版权声明
所有资源都来源于爬虫采集,如有侵权请联系我们,我们将立即删除