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的讀寫操作不要優化。

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

引用計數只是一個簡單的例子,更復雜的可以參考無鎖的數據結構


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