1.原子访问:Interlocked系列函数
LONG InterlockedExchangeAdd(
PLONG volatile plAddend,
LONG lIncrement);
LONGLONG InterlockedExchangedAdd64(
PLONGLONG volatile pllAddend,
LONGLONG llIncrement);
以上两个函数只要传一个长整形变量的地址和另一个增量值,函数就能保证递增操作是以原子方式进行的。
我们必须确保传给这些函数的变量地址是经过对齐的,否则这些函数可能会失败。
C运行库提供了一个_aligned_malloc函数,可以用这个函数来分配一块对齐过的内存。
其他原子操作函数还有:
1. InterlockedIncrement //给一个值加1
2. InterlockedExchange //值交换
3. InterlockedExchange64 //值交换
4. InterlockedExchangePointer //值交换
可以利用以上函数实现旋转锁,旋转锁的缺点是会占用CPU时间,试用于对资源占用时间短的情况。
PVOID InterlockedCompareExchange(
PLONG plDestination,
LONG lExchange,
LONG lComparand);
PVOID InterlockedCompareExchangePointer(
PVOID* ppvDestination,
PVOID pvExchange,
PVOID pvComparand);
这两个函数以原子的方式执行一个测试和设置操作。具体参见P202
2.关键段
首先定义一个名为g_cs的CRITICAL_SECTION数据结构,然后把任何需要访问的共享资源的代码放在EnterCriticalSection和LeaveCriticalSection之间
CRITICAL_SECTION g_cs;
EnterCriticalSection(&g_cs);
//...other code
LeaveCriticalSection(&g_cs);
任何线程试图访问被保护的资源之前都要调用一下函数对CRITICAL_SECTION 数据结构进行初始化
VOID InitializeCriticalSection(PCRITICAL_SECTION pcs);
当线程将不再需要访问共享资源的时候,应该调用下面的函数来清理CRITICAL_SECTION 数据结构
VOID DeleteCriticalSection(PCRITICAL_SECTION pcs);
也可以使用下面的函数来代替EnterCriticalSection,这个函数不会让线程进入等待状态,它会通过返回值来表示是否获准访问资源,资源正在被其他线程访问返回false,其它情况返回true。通过这个函数线程可以快速检测是否可以访问资源,如果不能可以去做些其他事情,而不用等待。
VOID TryEnterCriticalSection(PCRITICAL_SECTION pcs);
关键段和旋转锁
为了提高关键段的性能,Microsoft把旋转锁合并到关键段中。因此,当调用EnterCriticalSection的时候,它会用一个旋转锁不断的循环,尝试在一段时间内获得对资源的访问权。只有当尝试失败的时候,线程才会切换到内核模式并进入等待状态。因为切换内核模式的开销很大(大约1000个CPU时间)。
为了在使用关键段的时候同时使用关键所,必须使用下面的函数来初始化关键段:
BOOL InitializeCriticalSectionAndSpinCount(
PCRITICAL_SECTION pcs,
DWORD dwSpinCount);
第一个参数是关键段结构的地址。第二个参数是我们希望旋转锁循环的次数。
作者认为应该在使用关键段的同时总是使用旋转锁,难点在于循环次数的确定。用来保护进程堆的关键段所使用的旋转次数大约是4000,这可以作为我们的一个参考值。
3.Slim读/写锁
SRWLock的目的和关键段相同,不同的是SRWLock可以区分读写操作,多个线程可以对一个资源同时进行读操作,而当写操作的时候则需要进行资源保护。
使用方法:首先需要分配一个SRWLOCK结构并初始化:
VOID InitializeSRWLock(PSRWLOCK SRWLock);
初始化完成后调用以下函数获得对资源的独占访问权:
VOID AcquireSRWLockExclusive(PSRWLOCK SRWLock);
调用以下函数释放对资源的锁定:
VOID ReleaseSRWLockExclusive(PSRWLOCK SRWLock);
对于读取者线程则调用以下两个函数来操作资源:
VOID AcquireSRWLockShared(PSRWLOCK SRWLock);
VOID ReleaseSRWLockShared(PSRWLOCK SRWLock);
不需要删除或者销毁SRWLOCK结构,系统会自动清理。
总结一下,如果希望在应用程序中得到最佳性能,那么首先应该尝试不要共享数据,然后依次使用volatile读取,volatile写入,Interlocked API,SRWLock以及关键段。当且仅当所有这些都不满足要求的时候,再使用内核对象。