原子操作


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,則不需要原子操作,因為它們本身的操作就是具有原子性的。

參考:

http://www.cplusplus.com/reference/atomic/atomic/?kw=atomic

https://blog.csdn.net/zhangqhn/article/details/80876177


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM