8.1 原子访问:Interlocked系列函数(Interlock英文为互锁的意思)
(1)原子访问的原理
①原子访问:指的是一线程在访问某个资源的同时能够保证没有其他线程会在同一时刻访问该资源。
②从汇编的角度看,哪怕很简单的一条高级语言都可以被编译成多条的机器指令。在多线程环境下,这条语句的执行就可能被打断。而在打断期间,其中间结果可能己经被其他线程更改过,从而导致错误的结果。
③在Intelx86指令体系中,有些运算指令加上lock前缀就可以保证该指令操作的原子性。其原理是CPU执行该指令时发现该指令前加lock前缀,就会在总线维持一个硬件信号以阻止其他CPU(或线程)访问与该指令相同的目标内存地址。(注意是指令目标操作数的内存地址而且这些地址是经过内存对齐过的!)
④以InterlockedIncrement函数为例分析原子访问的原理
LONG InterlockedIncrement(LPLONG volatile lpAddend) { _asm{ //xadd指令两个功能:①交换(opd)→(ops);②(opd)←(opd)+(ops) mov eax,1 //前缀lock表示线程在执行该指令时,会将[ecx]内存锁 mov cx,Addend //定(实际上是在CPU总线上放一个信号)以标志该内存正在被使用, lock xadd [ecx],eax //从而阻止其他线程同时访问该内存。即其他线程要么在该指令之前, inc eax //要么在指令之后才能访问[ecx]指向的这块内存 } }
(2)Windows内核支持的整数原子操作——Interlocked***互锁函数
函数 |
描述 |
InterlockedIncrement InterlockedDecrement |
对LONG变量加(减)1,如:InterlockedIncrement(&g_iX) (内部使用lock xadd指令) |
InterlockedExchangeAdd |
将一个值加到一个LONG变量,返回变量原值,使用lock xadd指令,如:int g_iX = 0; // InterlockedExchangeAdd(&g_iX,-2); //g_iX -= 2; |
InterlockedCompareExchange |
InterlockedCompareExchange( plDest,lExchange, lComperand)。比如*plDestination==lComperand,如果相等将*plDest修改为lExchange,如果不等,则*plDest不变。返回值为*plDest原来的值。 (使用lock cmpxchag指令) |
InterlockedExchange InterlockedExchangePointer |
将第1个参数所指的内存里的当前值,以原子方式替换为第2个参数指定的值。函数返回值为原始值。后面那个函数是改变一个指针本身的值。(如果xchg指令,虽不加lock。但默认为原子操作) |
InterlockedOr |
对一个LONG变量做逻辑或运算,使用lock or指令 |
InterlockedAnd |
对一个LONG变量做逻辑与运算,使用lock and指令 |
InterlockedXor |
对一个LONG变量做逻辑异或运算,使用lock xor指令 |
(3)Interlocked单向链表函数
函数 |
描述 |
InterlockedSlistHead |
创建一个空栈 |
InterlockedPushEntrySList |
在栈顶添加一个元素 |
InterlockedPopentrySList |
移除位于栈顶的元素并将它返回 |
InterlockedFlushSlist |
清空栈 |
QueryDepthSlist |
返回栈中元素的数量 |
(4)利用InterlockedExchange实现自旋锁(spinlock)
//全局变量用来表明“共享资源”是否正在被使用 BOOl g_fResourceInUse = FALSE; void Func() { //等待资源的访问权——注意InterlockedExchange返回旧的值 while(InterlockedExchange(&g_fResourceInUse,TRUE)==TRUE) Sleep(0); //如果等不到锁,就休眼一下,以防止循环,浪费CPU。 //访问资源 …… //不再使用资源时 InterlockedExchange(&g_fResource,FALSE); //交出锁 }
①使用这项技术要极其小心,因为旋转锁通过循环实现,要耗费CPU时间,所以在while中加Sleep,可以改善这种状况,以避免浪费CPU。当然也可以用SwitchToThread代替,以便让出CPU这段时间,低优先级的线程也有被调用的机会。
②特别要注意的是,在单CPU的机器上要避免使用旋转锁,因为如果这个线程一直在不停循环,对CPU浪费大,也影响了其他线程改变锁的值,造成恶性循环。
③使用旋转锁的线程优先级要相同,否则如果等待锁的线程优先级高,则使用资源的线程可能会因分配不到CPU时间而无法释放锁。所以使用这种锁的线程要通过SetProcessPrirityBoost(或SetThreadPriortyBoost)来禁用系统动态提升线程优先级
④要确保锁变量和锁所保护的数据位于不同的高速缓存行(cache line),如果在同一高速缓存行,当使用资源的CPU更改了被保护的数据,会也会其他CPU相应的高速缓存行失效,这里等待锁的CPU还要从内存而不是CPU高速缓存行中读入锁的状态,这浪费了CPU时间。
⑤旋转锁是假定被保护资源始终只会占用一小段时间。与切换到内核模式的等待相比,这种通过循环方式的等待效率更高。
原文:http://www.cnblogs.com/5iedu/p/4719625.html