临界区概述:
用于多线程的互斥访问。如果有多个线程试图同时访问临界区,那么在有一个线程进入临界区后,其他试图访问的线程将被挂起,直到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到对临界区的互斥访问。(临界区中一般都是一个简短的代码段)
在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 thread's 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)
DebugInfo:指向一个调试用的数据,该结构的类型为RTL_CRITICAL_SECTION_DEBUG LockCount: 初始值-1,若结果大于等于0,表示该临界区已被线程占用。 OwningThread: 当前拥有临界区的线程 RecursionCount:所有者线程连续进入临界区的次数 LockSemaphore: 内核对象句柄,用于告知操作系统,该临界区目前处于空闲状态,用于唤醒因等待临界区而挂起的线程
"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."
临界区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. 若有其他线程在等待,唤醒等待线程
( http://msdn.microsoft.com/zh-cn/magazine/cc164040(en-us).aspx)
情况下, 通过链表数据结构,可以访问到数据的临界区对象。
自旋:
对于临界区的操作,(EnterCriticalSection)操作,采用的是主动进入临界区。意思就是,当没有能够进入时,不停的主动尝试进入,直至进入为止。这种主动进入的方式,称为自旋(也叫忙等待)。
被动方式:获取不到后,进入等待队列,当要获取的对象被释放后,系统唤醒等待的线程,这个方式叫做被动方式。
小结:
1.进入灵界区和离开临界区是成对操作,进入临界区必须要有离开临界区否则临界区保护的共享资源将永远不会被释放。
2.在使用临界区时,临界区间使用的代码最好简短,减少其他线程的等待时间,提高程序性能。
3.临界区同步速度很快,但却只能用来同步本进程内的线程,而不可用来同步多个进程中的线程。
4. 临界区是用户态下的对象,非内核对象,所以在使用时无需再用户态和内核态之间切换,效率明显要比其他用户互斥的内核对象高。
参考:
http://msdn.microsoft.com/zh-cn/magazine/cc164040(en-us).aspx