1、原子锁
使用InterlockedExchangeAdd函数来实现原子增长,InterlockedExchange\InterlockedExchangePointer用来交换两个变
量的值,InterlockedCompareExchange对比数值,相等则交换(对应的InterlockedCompareExchangePointer)。对应的
还有64位函数。
InterlockedIncrement\InterlockedDecrement是比较老的函数,只能增加或递减1,InterlockedExchangeAdd的灵活性更
大。
2、Interlocked 单向链表操作函数(支持原子操作的链表)
InitializeSListHead
创建一个空栈
InterlockedPushEntrySList
入栈
InterlockedPopEntrySList
出栈
InterlockedFlushSList
清空栈
QueryDepthSList
获取栈元素个数
3、高速缓存行
CPU从内存中取出指令时,一次取出高速缓存行大小个字节(32、64、128因CPU型号而异),CPU就不用访问内存总线,直
接从缓存中读取指令比内存中读取快多了。
由于多个CPU取出同一块内存数据到各自的高速缓存行,导致内存数据不一致。CPU设计时,当一个CPU修改了它的告诉缓
存行后,其他CPU会收到通知,并使自己的高速缓存行作废。
这意味着,我们应该根据高速缓存行的大小来讲应用程序的数据组织在一起,并将数据与缓存行的边界对齐。这样做的目
的是为了确保不同的CPU能各自访问不同的内存地址,而且这些地址不在同一个高速缓冲行内。
4、使用volatile关键字
volatile告诉编译器,不要对这个变量进行任何形式的优化,而是始终从变量所在内存中的位置读取变量的值。
编译器的优化,编译器读取数据到CPU寄存器中,下次需要该数值时直接取寄存器中的值,这样即使变量数值已经改变也
无法知道。使用volatile后,CPU每次都去变量所在内存读取,变量改变后可以及时获取。
5、使用关键段(临界区)CRITICALSECTION
EnterCriticalSection\LeaveCriticalSection,使用前需要初始化InitializeCriticalSection,使用后需要释放
DeleteCriticalSection。
关键段优点:易使用,执行速度快;缺点:无法用在多个进程之间对线程进行同步。
同时有两个线程访问资源时,EnterCriticalSection会使一个获得资源,另一个切换到等待状态。如果等待时间太长,
最终回引发异常,超时时间保存在注册表:HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\
Session Manager\CriticalSectionTimeout中。默认大约是30天。
可以使用TryEnterCriticalSection来替代EnterCriticalSection,TryEnterCriticalSection会立即返回资源是否正在
被占用。
6、关键段和旋转锁
当一个线程试图进入一个关键段,而这个关键段正在被另一个线程占用时,函数会立即把调用线程切换到等待状态。这
意味着线程必须从用户模式切换到内核模式(大约1000个CPU周期),这个开销十分大。为了提高关键段的性能,
Microsoft把旋转锁合并到了关键段中。调用EnterCriticalSection时,会调用一个旋转锁不断循环,尝试获得资源的
访问权。只有在尝试失败后,才会切换进内核状态。为了使用带旋转锁的关键段,需要使用API
InitializeCriticalSectionAndSpinCount来初始化关键段,第二个参数制定循环的次数,如果是在单CPU机器上这个
参数将会被忽略。
SetCriticalSectionSpinCount用来改变旋转锁循环次数。用来保护进程堆的关键段所使用的循环次数大约是4000,这可以作为我们的一个参考值。
EnterCriticalSection在内存不足时会导致异常,但是无返回值;InitializeCriticalSectionAndSpinCount在内存不足时,返回值为FALSE,便于直接测试是否创建成功。
7、Slim读写锁
在Vista以上本版才有此函数,忽略。
8、以上几种线程同步方式的性能:
如果希望应用程序得到最佳性能,首先尝试不要共享数据,然后依次使用volatile读取、volatile写入,Interlocked
API,SRWLock以及关键段。当前仅当这些都不能满足要求时,再使用内核对象(每次都需要在用户模式和内核模式之间
切换,CPU开销十分大)。
9、使用技巧
(1)以原子方式操作一组对象时使用一个锁,缺点是降低了可伸缩性:任何时刻系统只允许一个线程运行。
(2)同时访问多个资源时,每个资源都有自己的锁。在获取资源时,所有线程必须以相同顺序来执行。
(3)不要长时间占用锁,其他线程可能进入等待状态,影响程序性能
以下是测试代码:
<span style="white-space:pre"> </span>//获取CPU核心数以及每个CPU的高速缓存行的大小
<span style="white-space:pre"> </span>PSYSTEM_LOGICAL_PROCESSOR_INFORMATION buffer = NULL; DWORD dwLen = 0; while( true ) { if ( GetLogicalProcessorInformation(buffer, &dwLen) ) break; if ( GetLastError() != ERROR_INSUFFICIENT_BUFFER ) { cout<<"Error code = "<<GetLastError()<<endl; return 1; } if ( buffer ) free(buffer); buffer = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION)malloc(dwLen); if ( NULL == buffer ) { cout<<"Error to malloc"<<endl; return 2; } } int nProcCoreCount = 0; int nByteOffset = 0; PSYSTEM_LOGICAL_PROCESSOR_INFORMATION ptr = buffer; while( nByteOffset<dwLen ) { switch( ptr->Relationship ) { case RelationProcessorCore: nProcCoreCount++; break; case RelationCache: cout<<"cpu"<<nProcCoreCount<<"'s cache size is "<<ptr->Cache.LineSize<<"byte"<<endl; break; default: break; } nByteOffset += sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION); ptr++; } cout<<"ProcessorCore count is "<<nProcCoreCount<<endl; free(buffer);
/*////////////////////////////////////////// //旋转锁 CPU不断比较两个值,会消耗CPU时间。这里假定所有线程都以相同的优先级运行,对于需要用旋转锁的线程,可能需要使用 SetProcessPriorityBoost或者SetThreadPriorityBoost函数来禁止线程优先级提升。 在只有单处理器上的机器不应该使用旋转锁,否则容易造成死锁。 */ DWORD WINAPI Thread1(LPVOID lpParam) { while( InterlockedExchange(&g_bUse, TRUE) == TRUE ) {//返回TRUE表示正在被使用,继续等待 Sleep(0); } //返回FALSE,表示当前没有被使用,我们已经将其设置为正在被使用 //do something g_nIndex++; Sleep(300); //使用完了后,设置状态为未使用 InterlockedExchange(&g_bUse, FALSE); return 0; }