1、原子操作
原子操作(atomic operation)指的是由多步操作組成的一個操作。如果該操作不能原子地執行,則要么執行完所有步驟,要么一步也不執行,不可能只執行所有步驟的一個子集。
現代操作系統中,一般都提供了原子操作來實現一些同步操作,所謂原子操作,也就是一個獨立而不可分割的操作。在單核環境中,一般的意義下原子操作中線程不會被切換,線程切換要么在原子操作之前,要么在原子操作完成之后。更廣泛的意義下原子操作是指一系列必須整體完成的操作步驟,如果任何一步操作沒有完成,那么所有完成的步驟都必須回滾,這樣就可以保證要么所有操作步驟都未完成,要么所有操作步驟都被完成。
例如在單核系統里,單個的機器指令可以看成是原子操作(如果有編譯器優化、亂序執行等情況除外);在多核系統中,單個的機器指令就不是原子操作,因為多核系統里是多指令流並行運行的,一個核在執行一個指令時,其他核同時執行的指令有可能操作同一塊內存區域,從而出現數據競爭現象。多核系統中的原子操作通常使用內存柵障(memory barrier)來實現,即一個CPU核在執行原子操作時,其他CPU核必須停止對內存操作或者不對指定的內存進行操作,這樣才能避免數據競爭問題。
在C++11之前,C++標准中並沒有對原子操作進行規定。vs和gcc編譯器提供了原子操作的api。
2、windows原子操作api
Win32 API中常用的原子操作主要有三類,一種是加1減1操作,一種是比較交換操作,另外一種是賦值(寫)操作。
(1)原子加1減1操作
LONG InterlockedIncrement( LONG volatile* Addend);
LONG InterlockedDecrement( LONG volatile* Addend);
(2) 比較並交換操作
LONG InterlockedCompareExchange( LONG volatile*Destination, LONG Exchange, LONG Comperand );
這個操作是先將Comperand的值和Destination指向變量的值進行比較,如果相等就將Exchange變量的值賦給Destination指向的變量。返回值為未修改前的Destination位置的初始值。
(3)原子寫操作
LONG InterlockedExchange( LONG volatile* Target, LONG Value);
InterlockedExchange的作用為將Value的值賦給Target指向的變量,返回Target指向變量未被賦值前的值。
3、GCC編譯器提供的原子操作API
type __sync_fetch_and_add (type *ptr, type value);
type __sync_fetch_and_sub (type *ptr, type value);
type __sync_fetch_and_or (type *ptr, type value);
type __sync_fetch_and_and (type *ptr, type value);
type __sync_fetch_and_xor (type *ptr, type value);
type __sync_fetch_and_nand (type *ptr, type value);
type __sync_add_and_fetch (type *ptr, type value);
type __sync_sub_and_fetch (type *ptr, type value);
type __sync_or_and_fetch (type *ptr, type value);
type __sync_and_and_fetch (type *ptr, type value);
type __sync_xor_and_fetch (type *ptr, type value);
type __sync_nand_and_fetch (type *ptr, type value);
4、C++11提供的原子操作
C++11中在<atomic>中定義了atomic模板類,atomic的模板參數類型可以為int、long、bool等等,C++中稱為trivially copyable type。atomic_int、atomic_long為atomic模板實例化后的宏定義。atomic具體的原子操作函數可以參考http://www.cplusplus.com/reference/atomic/atomic/?kw=atomic。
5、原子操作的效率
(1)不加鎖也不使用原子變量的程序
程序代碼:


(2)加鎖的程序
程序代碼:


3.使用C++11原子變量的程序
程序代碼和輸出:


(4)結論
上面的第一個不加鎖程序肯定是最不推薦使用的,因為它的執行結果都不正確。第二個程序是使用的常規鎖來解決問題,結果正確,但是耗時較久。第三個程序使用的是C++11引入的原子數據類型,使用它程序結果正確,在運行速度上也比加鎖的版本快很多。所以,在我們平常寫程序的過程中,推薦使用C++11引入的原子變量。
6、什么時候使用原子操作
在多線程並發的條件下,所有不是原子性的操作需要保證原子性時,都需要進行原子操作處理。
例:
long count = 0;
void func()
{
count++;
}
如果有n個線程同時執行這段代碼,所有線程執行完后,count的值不一定等於n。因為count++不是一個原子操作,編譯成匯編代碼,如下所示:
MOV eax, [count]
INC eax
MOV [count], eax
在cpu執行時
第一步,先將 count所在內存的值加載到寄存器;
第二步,將寄存器的值自增1;
第三步,將寄存器中的值寫回內存。
所以當第一個線程將count值加載到寄存器,並完成自增1,這時寄存器中的值為2,如果此時cpu調度將此線程中斷,並執行完其它線程后,再將此線程調度執行,此時,會將2寫入到count。count最后的值就成了2。如果要確保改結果正確,那么cout++就要使用原子操作類型。
上述示例中,count的操作如果為count = count + 2,那么也需要原子操作,而如果為count=2或者count==2,則不需要原子操作,因為它們本身的操作就是具有原子性的。
參考: