無鎖同步 C 11之Atomic和CAS

2022-03-10 04:48:09 字數 2338 閱讀 4897

我們知道在c++11中引入了mutex和方便優雅的lock_guard。但是有時候我們想要的是效能更高的無鎖實現,下面我們來討論c++11中新增的原子操作類atomic,我們可以利用它巧妙地實現無鎖同步。

1 #include 2 #include 3

4 #include 5

6using

namespace

std;78

mutex g_mutex;

9int g_count = 0;10

11int

main()

1218

});19

20thread thr2(()

25});

2627

thr1.join();

28thr2.join();

2930 cout << g_count <3132

33 }

在上述例子中,如果把①②的鎖注釋後,我們可能無法得到正確的結果。原因是c++並沒有給我們保證+=操作具有原子性(其本質應該是讀-加-寫3個操作)。

c++11給我們帶來的atomic一系列原子操作類,它們提供的方法能保證具有原子性。這些方法是不可再分的,獲取這些變數的值時,永遠獲得修改前的值或修改後的值,不會獲得修改過程中的中間數值。

這些類都禁用了拷貝建構函式,原因是原子讀和原子寫是2個獨立原子操作,無法保證2個獨立的操作加在一起仍然保證原子性。

這些類中,最簡單的是atomic_flag(其實和atomic相似),它只有test_and_set()和clear()方法。其中,test_and_set會檢查變數的值是否為false,如果為false則把值改為true。

除了atomic_flag外,其他型別可以通過atomic獲得。atomic提供了常見且容易理解的方法:

store

load

exchange

compare_exchange_weak

compare_exchange_strong

store是原子寫操作,而load則是對應的原子讀操作。

exchange允許2個數值進行交換,並保證整個過程是原子的。

而compare_exchange_weak和compare_exchange_strong則是著名的cas(compare and set)。引數會要求在這裡傳入期待的數值和新的數值。它們對比變數的值和期待的值是否一致,如果是,則替換為使用者指定的乙個新的數值。如果不是,則將變數的值和期待的值交換。

weak版本的cas允許偶然出乎意料的返回(比如在字段值和期待值一樣的時候卻返回了false),不過在一些迴圈演算法中,這是可以接受的。通常它比起strong有更高的效能。

下面舉個簡單的例子,使用cas操作實現乙個不帶鎖的併發棧。這個例子從《c++併發程式設計》摘抄而來。

在非併發條件下,要實現乙個棧的push操作,我們可能有如下操作:

新建乙個節點

將該節點的next指標指向現有棧頂

更新棧頂    

但是在併發條件下,上述無保護的操作明顯可能出現問題。下面舉乙個例子:

原棧頂為a。(此時棧狀態: a->p->q->...,我們約定從左到右第乙個值為棧頂,p->q代表p.next = q)

執行緒1準備將b壓棧。執行緒1執行完步驟2後被強佔。(新建節點b,並使 b.next = a,即b->a)

執行緒2得到cpu時間片並完成將c壓棧的操作,即完成步驟1、2、3。此時棧狀態(此時棧狀態: c->a->...)

這時執行緒1重新獲得cpu時間片,執行步驟3。導致棧狀態變為(此時棧狀態: b->a->...)

結果執行緒2的操作丟失,這顯然不是我們想要的結果。

那麼我們如何解決這個問題呢?只要保證步驟3更新棧頂時候,棧頂是我們在步驟2中獲得頂棧頂即可。因為如果有其它執行緒進行操作,棧頂必然改變。

我們可以利用cas輕鬆解決這個問題:如果棧頂是我們步驟2中獲取頂棧頂,則執行步驟3。否則,自旋(即重新執行步驟2)。

因此,不帶鎖的壓棧push操作比較簡單。

1 template2

class

lock_free_stack313

};14

15 std::atomichead;

16public:17

void push(t const&data)

1823 };

我們可以注意到乙個非常巧妙的設計。在push方法裡,atomic_compare_exchange_weak如果失敗,證明有其他執行緒更新了棧頂,而這個時候被其他執行緒更新的新棧頂值會被更新到new_node->next中,因此迴圈可以直接再次嘗試壓棧而無需由程式設計師更新

new_node->next

。2017.3.14:

發現原文pop部分有錯誤,所以暫時刪除

C 11之atomic原子操作

atomic對int char bool等資料結構進行了原子性封裝,在多執行緒環境中,對std atomic物件的訪問不會造成競爭 冒險。利用std atomic可實現資料結構的無鎖設計。所謂的原子操作,取的就是 原子是最小的 不可分割的最小個體 的意義,它表示在多個執行緒訪問同乙個全域性資源的時候...

2執行緒同步 C 11中的互斥鎖

c11中mutex標頭檔案內容 mutex 類,基本的互斥鎖 recursive mutex 類,同一執行緒可以遞迴呼叫的互斥鎖 timed mutex 類,在指定的時間內能返回的鎖 recursive timed mutex 類,在指定的時間內能返回且同一執行緒能遞迴呼叫的鎖 adopt lock...

C 11 多執行緒同步 互斥鎖 條件變數

在多執行緒程式中,執行緒同步 多個執行緒訪問乙個資源保證順序 是乙個非常重要的問題,linux下常見的執行緒同步的方法有下面幾種 這篇部落格只介紹互斥量和條件變數的使用。通常情況下,互斥鎖和條件變數是配合使用的,互斥鎖用於短期鎖定,主要保證執行緒對臨界區的進入 條件變數用於執行緒長期等待,在wait...