原子操作——原理與底層實現

一.概述

原子操作(atomic operation)指的是由多步操作組成的一個操作。如果該操作不能原子地執行,則要麼執行完所有步驟,要麼一步也不執行,不可能只執行所有步驟的一個子集。

現代操作系統中,一般都提供了原子操作來實現一些同步操作,所謂原子操作,也就是一個獨立而不可分割的操作。在單核環境中,一般的意義下原子操作中線程不會被切換,線程切換要麼在原子操作之前,要麼在原子操作完成之後。更廣泛的意義下原子操作是指一系列必須整體完成的操作步驟,如果任何一步操作沒有完成,那麼所有完成的步驟都必須回滾,這樣就可以保證要麼所有操作步驟都未完成,要麼所有操作步驟都被完成。

例如在單核系統裏,單個的機器指令可以看成是原子操作(如果有編譯器優化、亂序執行等情況除外);在多核系統中,單個的機器指令就不是原子操作,因爲多核系統裏是多指令流並行運行的,一個核在執行一個指令時,其他核同時執行的指令有可能操作同一塊內存區域,從而出現數據競爭現象。多核系統中的原子操作通常使用內存柵障(memory barrier)來實現,即一個CPU核在執行原子操作時,其他CPU核必須停止對內存操作或者不對指定的內存進行操作,這樣才能避免數據競爭問題。

在C++11之前,C++標準中並沒有對原子操作進行規定。vs和gcc編譯器提供了原子操作的api。

二.原子操作的底層實現

實現方式:基於緩存加鎖與總線加鎖。

1.總線加鎖

所謂總線鎖就是使用處理器提供的一個lock#信號,當一個處理器在總線上輸出此信號時,其他處理器的請求會被阻塞住,那麼該處理器可以獨佔共享內存。

但總線鎖定把cpu和內存之間的通信鎖住了,這使得鎖定期間,其他處理器不能操作其他內存地址的數據,所以開銷比較大。

2.緩存加鎖

第二個機制是通過緩存鎖定來保證原子性。在同一時刻,我們只需保證對某個內存地址的操作是原子性即可,但總線鎖定把CPU和內存之間的通信鎖住了,這使得鎖定期間,其他處理器不能操作其他內存地址的數據,所以總線鎖定的開銷比較大,目前處理器在某些場合下使用緩存鎖定代替總線鎖定來進行優化。

頻繁使用的內存會緩存在處理器的L1、L2和L3高速緩存裏,那麼原子操作就可以直接在處理器內部緩存中進行,並不需要聲明總線鎖,在Pentium 6和目前的處理器中可以使用“緩存鎖定”的方式來實現複雜的原子性。所謂“緩存鎖定”是指內存區域如果被緩存在處理器的緩存行中,並且在Lock操作期間被鎖定,那麼當它執行鎖操作回寫到內存時,處理器不在總線上聲言LOCK#信號,而是修改內部的內存地址,並允許它的緩存一致性機制來保證操作的原子性,因爲緩存一致性機制會阻止同時修改由兩個以上處理器緩存的內存區域數據,當其他處理器回寫已被鎖定的緩存行的數據時,會使緩存行無效,在如圖2-3所示的例子中,當CPU1修改緩存行中的i時使用了緩存鎖定,那麼CPU2就不能同時緩存i的緩存行。
但是有兩種情況下處理器不會使用緩存鎖定。
第一種情況是:當操作的數據不能被緩存在處理器內部,或操作的數據跨多個緩存行(cache line)時,則處理器會調用總線鎖定。
第二種情況是:有些處理器不支持緩存鎖定。對於Intel 486和Pentium處理器,就算鎖定的內存區域在處理器的緩存行中也會調用總線鎖定。
針對以上兩個機制,我們通過Intel處理器提供了很多Lock前綴的指令來實現。例如,位測試和修改指令:BTS、BTR、BTC;交換指令XADD、CMPXCHG,以及其他一些操作數和邏輯指令(如ADD、OR)等,被這些指令操作的內存區域就會加鎖,導致其他處理器不能同時訪問它。

三.編程使用

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指向變量未被賦值前的值。

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);

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。

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