CriticalSection
typedef RTL_CRITICAL_SECTION CRITICAL_SECTION;
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;
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;
LockCount 这是临界区中最重要的一个字段。它被初始化为数值 -1;这个负数你换算成8位二进制,最后一位0表示临界区被锁,1表示未锁,倒数第二位用0和1表示是否有线程被唤醒,剩下的几位你取反所得的十进制的值就等待的线程数。比如-22,二进制就是11101010,最后一位是0表示临界区被锁,倒数第二位1表示有线程被唤醒,最后几位111010取反是101,表示5个线程正在等待;
RecursionCount
此字段包含所有者线程已经获得该临界区的次数。如果该数值为零,下一个尝试获取该临界区的线程将会成功。每EnterCriticalSection一次,这个值就会加一,LeaveCriticalSection一次就会减一。
OwningThread
此字段包含当前占用此临界区的线程的线程标识符。
LockSemaphore
此字段的命名不恰当,它实际上是一个自复位事件,而不是一个信号。它是一个内核对象句柄,用于通知操作系统:该临界区现在空闲。操作系统在一个线程第一次尝试获得该临界区,但被另一个已经拥有该临界区的线程所阻止时,自动创建这样一个句柄。应当调用 DeleteCriticalSection释放了这个内核对象,否则将会发生资源泄漏。
SpinCount 仅用于多处理器系统。MSDN 文档对此字段进行如下说明:“在多处理器系统中,如果该临界区不可用,调用线程将在对与该临界区相关的信号执行等待操作之前,旋转 dwSpinCount 次。如果该临界区在旋转操作期间变为可用,该调用线程就避免了等待操作。”旋转计数可以在多处理器计算机上提供更佳性能,其原因在于在一个循环中旋转通常要快于进入内核模式等待状态。此字段默认值为零,但可以用
InitializeCriticalSectionAndSpinCount 将其设置为一个不同值。
注:
如果在一个线程里EnterCriticalSection(&g_cs),再在另一个线程里LeaveCriticalSection(&g_cs),那么此g_cs会和在一个线程里LeaveCriticalSection(&g_cs)效果是一样的。目前看来的确如此。
Semphore
HANDLE CreateSemaphore(
__in_opt LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
__in LONG lInitialCount, //当前资源计数
__in LONG lMaximumCount, //最大资源计数
__in_opt LPCSTR lpName
);
若lInitialCount>lMaximumCount,会创建失败。
HANDLE CreateSemaphoreExA(
__in_opt LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
__in LONG lInitialCount,
__in LONG lMaximumCount,
__in_opt LPCSTR lpName,
__reserved DWORD dwFlags, //设置为0
__in DWORD dwDesiredAccess //访问权限
);
BOOL ReleaseSemaphore(
__in HANDLE hSemaphore,
__in LONG lReleaseCount,
__out_opt LPLONG lpPreviousCount
);
若当前资源计数
WaitForSingleObject(g_hSemphore,INFINITE)会使当前资源计数减一。
现在以生产者消费者模式来举例说明信号量机制。有一个缓存最多可以放5个产品,那么创建信号量时,lInitialCount参数传0,lMaximumCount参数传5,0表示当前还没生产出产品,5代表缓存最多可以放5个产品。有N1个线程作为生产者,每生产一个产品,调用一次ReleaseSemaphore,lReleaseCount传1,当前资源计数便会加1,这表示生产了一个消费者。当当前资源计数大于0了,因为在消费者线程中,一直在WaitForSingleObject,某一个线程被触发,当前资源计数减1,表示消费了一个产品。
若生产者生产了三个产品放到了缓存中,此时资源计数为3,那么便可以有三个消费者线程消费产品。只有一个缓存,即为公共资源,却可能有三个线程去访问它,这时却没有互斥机制保护缓存这个公共资源。所以最好在访问缓存的时候加一个临界区或互斥量保护缓存这个资源。
跟 CriticalSections 和 Mutex 不同,Semaphores不是排他性占有。临界区和互斥量同一时间内只能有单一线程获得目标并拥有操作的权利,而 Semaphores 则不是这样,同一时间内可以有多个线程获得目标并操作。
Mutex
HANDLE CreateMutexA(
__in_opt LPSECURITY_ATTRIBUTES lpMutexAttributes,
__in BOOL bInitialOwner,//TRUE则初始化为调用线程所有,FALSE则不为任何线程所有
__in_opt LPCSTR lpName
);
BOOL ReleaseMutex(
__in HANDLE hMutex
);
WaitForSingleObject查看等待成功一次,使用计数加1,便去执行访问资源代码,这时候在此线程中再WaitForSingleObject一次,使用计数加1变为2,依旧成功,但是这种情况下,就要ReleaseMutex两次了,否则,其他线程不能WaitForSingleObject成功。
slim读写锁
从上面可以看出来读写锁并不是读写文件的意思,而是“读”与“写”操作。写独占,读共享。
VOID InitializeSRWLock (
__out PSRWLOCK SRWLock
);
typedef struct _RTL_SRWLOCK {
PVOID Ptr;
} RTL_SRWLOCK, *PRTL_SRWLOCK;
typedef RTL_SRWLOCK SRWLOCK, *PSRWLOCK;
读操作:
BOOLEAN WINAPI AcquireSRWLockExclusive( PSRWLOCK SRWLock );
VOID WINAPI ReleaseSRWLockExclusive ( PSRWLOCK SRWLock );
写操作:
VOID WINAPI AcquireSRWLockShared ( PSRWLOCK SRWLock );
VOID WINAPI ReleaseSRWLockShared ( PSRWLOCK SRWLock );
写独占,读共享,这句话是啥意思呢,意思是说:写占之前,既不能写占也不能读占,写占之后既不能写占,也不能读占;读占之前,不可以写占,可以读占,读占之后,不可以写占,但可以读占。
AcquireSRWLockExclusive( SRWLock );
AcquireSRWLockExclusive( SRWLock );
AcquireSRWLockExclusive( SRWLock );
AcquireSRWLockShared( SRWLock );
AcquireSRWLockShared( SRWLock );
AcquireSRWLockShared( SRWLock );
AcquireSRWLockShared( SRWLock );
AcquireSRWLockExclusive( SRWLock );
上面四种情况,只有第三种情况,即读读,第二句代码才不会进入等待状态,其余三种情况第二句代码都会进入等待状态。
AcquireSRWLockExclusive(&srwLock);
ReleaseSRWLockShared(&srwLock);
AcquireSRWLockExclusive(&srwLock);
//写占,却读释放,写占失败。
AcquireSRWLockExclusive(&srwLock);
ReleaseSRWLockShared(&srwLock);
AcquireSRWLockShared(&srwLock);
//写占,却读释放,读占成功。
AcquireSRWLockShared(&srwLock);
ReleaseSRWLockExclusive(&srwLock);
AcquireSRWLockShared(&srwLock);
//读占,却写释放,读占成功。
AcquireSRWLockShared(&srwLock);
ReleaseSRWLockExclusive(&srwLock);
AcquireSRWLockExclusive(&srwLock);
//读占,却写释放,写占成功。
虽然上面有释放成功的情况,但是还是配套使用比较好。