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