LOCK instruction and InterLocked* functions

一个最简单的例子,在多线程环境中,我们会常常使用到”引用计数”的情况,如变量

int g_ref = 0;

全局变量g_ref的读写是多线程不安全的,这是因为相关操作是一个read-modify-write过程,因此需要使用同步机制,如考虑Critical Section、Mutex,于是有如下最直观的两种解决方案:

(1)

CRITICAL_SECTION g_cs;

EnterCriticalSection(&g_cs);

g_ref++;

LeaveCriticalSection(&g_cs);

或者

(2)

HANDLE g_mutex;

WaitForSingleObject(g_mutex, INFINITE);

g_ref++;

ReleaseMutex(g_mutex);

对这两种方案进行比较,发现(2)总是需要陷入内核,从而选择第(1)种

-----------------------------------------------------------------------------------

然而事情并未到这儿结束,我们还可以深入一步!

引用计数相关操作通常可认为是一个原子操作(Atomic Operation),于是可以使用InterLocked API改写(1)

InterLockedIncrement((LONG volatile*)&g_ref);

其中InterLockedIncrement的原型为

LONG __cdecl InterLockedIncrement(LONG volatile* Addend);

至于g_ref的其它操作,如g_ref--, g_ref+=n, g_ref-=n等,类似的InterLocked API如

InterLockedDecrement, InterLockedExchangeAdd

至于InterLocked为何被认为是原子操作,我们来看看反汇编代码

mov         ecx,dword ptr [esp+4] 
mov         eax,1 
lock     xadd   dword ptr [ecx],eax 
inc           eax  
ret            4    

其中核心代码为

lock     xadd   dword ptr [ecx],eax 

其中令它成为原子操作的关键在于LOCK指令

----------------------------------------------------------------------------------

LOCK指令:

当某些指令被LOCK修饰时,在多核环境中,能保证在操作共享内存时能在一个原子操作内完成,此时目的操作数必须为内存操作数。

能使用LOCK修饰的指令包括:ADD, ADC, AND, BTC, BTR, BTS, CMPXCHG, CMPXCH8B, DEC, INC, NEG, NOT, OR, SBB, SUB, XOR, XADD, XCHG。

当使用XCHG指令时,即使没显式使用LOCK,CPU也会会认其成为一个原子操作。


这种情况下,我们发现,我们能将read-modify-write在一个指令周期内完成,这就是所谓的原子操作。

上述指令的高级语言版本封装实现基本上都能找到,如微软中的InterLocked APIs。

同时,在使用高级语言的相关函数时,为了更直观一些,我们也许应该这样声明引用计数变量:

LONG volatile g_ref = 0;

其中volatile的作用是告诉编译器对g_ref的读写操作不要优化。

----------------------------------------------------------------------------------

引用计数只是一个简单的例子,更复杂的可以参考无锁的数据结构


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章