Interlocked系列函數可以對內存進行原子操作,它是如何實現的?
它的實現依賴於底層的CPU架構。對於某些CPU來說,這很簡單,例如x86可以通過
LOCK前綴直接支持Interlocked操作(有一個額外的特性就是XCHG指令總是隱式包含了LOCK前綴)。IA64和x64也直接支持原子的load-modify-store操作。
其它的多數CPU架構把這個操作分成兩部分,被稱為
Load-link/store-conditional。第一部分(load-link)從指定內存地址讀取一個值,並且處理器會監視這個內存地址,看是否有其它處理器修改該值。第二部分(store-conditional)是如果這期間沒有其它處理器修改該值,則將新值存回該地址。因此,一個原子的load-link/store-conditional操作就是通過load-link讀取值,進行一些計算,然后試圖store-conditional。如果store-conditional失敗,那么重新開始整個操作。
1 LONG InterlockedIncrement( LONG volatile *value ) 2 { 3 LONG lOriginal, lNewValue; 4 do 5 { 6 // 7 //通過load-link讀取當前值 8 //可以知道寫回之前是否有人修改它 9 // 10 lOriginal = load_link(value); 11 12 // 13 //計算新的值 14 // 15 lNewValue = lOriginal + 1; 16 17 // 18 //有條件的寫回新值 19 //如果有人在計算期間覆寫該值,則函數返回失敗 20 // 21 } while ( !store_conditional(value, lNewValue)); 22 return lNewValue; 23 }
(如果看起來有些熟悉,是的,你之前見到過這種模式。)
請求CPU監視一個內存地址依賴於CPU自己的實現。但要記住一件事情,CPU在同一時間只能監視一個內存地址,並且這個時間是很短暫的。如果你的代碼被搶占了或者在load-link后有一個硬件中斷到來,那么你的store-conditional將會失敗,因為CPU因為硬件中斷而分心了,完全忘記了你要求它監視的內存地址(即使CPU成功的記住了它,也不會記太久,因為硬件中斷幾乎都會執行自己的load-link指令,因此會替換成它自己要求監視的內存地址)。
另外,CPU可能會有點懶,在監視時並不監視內存地址,而是監視cache line,如果有人修改了一個不同的內存位置,但是剛好跟要被監視的內存地址在同一個cache line里,store-conditional操作也會失敗,即使它事實上可以成功完成。ARM架構的CPU是太懶了,以至於任何向同一塊2048字節寫入的操作都會導致store-conditional失敗。
這對於需要用匯編語言來實現Interlocked操作的你來說意味着什么?你需要盡可能減少load-link和store-conditional之間的指令數。例如,InterlockedIncrement只不過是給值加1。你在load-link和store-conditional之間插入的指令越多,store-conditional失敗的可能就越大,你就不得不重來一次。如果你在兩者之間插入的指令太多了就會導致store-conditional永遠不會成功。舉一個極端的例子,如果你計算新值的代碼需要耗時5秒,在這5秒內肯定會接收到很多硬件中斷,store-conditional操作就永遠都會失敗。
本文譯自The Old New Thing,原文地址http://blogs.msdn.com/b/oldnewthing/archive/2013/09/13/10448736.aspx。