C++11 併發指南已經寫了 5 章,前五章重點介紹了多線程編程方面的內容,但大部份內容只涉及多線程、互斥量、條件變量和異步編程相關的 API,C++11 程序員徹底能夠沒必要知道這些 API 在底層是如何實現的,只須要清楚 C++11 多線程和異步編程相關 API 的語義,而後熟加練習便可應付大部分多線程編碼需求。可是在不少極端的場合下爲了性能和效率,咱們須要開發一些 lock-free 的算法和數據結構,前面幾章的內容可能就派不上用場了,所以從本文開始介紹 C++11 標準中 <atomic> 頭文件裏面的類和相關函數。ios
本文介紹 <atomic> 頭文件中最簡單的原子類型: atomic_flag。atomic_flag 一種簡單的原子布爾類型,只支持兩種操做,test-and-set 和 clear。程序員
std::atomic_flag 構造函數以下:算法
std::atomic_flag 只有默認構造函數,拷貝構造函數已被禁用,所以不能從其餘的 std::atomic_flag 對象構造一個新的 std::atomic_flag 對象。編程
若是在初始化時沒有明確使用 ATOMIC_FLAG_INIT初始化,那麼新建立的 std::atomic_flag 對象的狀態是未指定的(unspecified)(既沒有被 set 也沒有被 clear。)另外,atomic_flag不能被拷貝,也不能 move 賦值。數據結構
ATOMIC_FLAG_INIT: 若是某個 std::atomic_flag 對象使用該宏初始化,那麼能夠保證該 std::atomic_flag 對象在建立時處於 clear 狀態。多線程
下面先看一個簡單的例子,main() 函數中建立了 10 個線程進行計數,率先完成計數任務的線程輸出本身的 ID,後續完成計數任務的線程不會輸出自身 ID:併發
#include <iostream> // std::cout #include <atomic> // std::atomic, std::atomic_flag, ATOMIC_FLAG_INIT #include <thread> // std::thread, std::this_thread::yield #include <vector> // std::vector std::atomic<bool> ready(false); // can be checked without being set std::atomic_flag winner = ATOMIC_FLAG_INIT; // always set when checked void count1m(int id) { while (!ready) { std::this_thread::yield(); } // 等待主線程中設置 ready 爲 true. for (int i = 0; i < 1000000; ++i) { } // 計數. // 若是某個線程率先執行完上面的計數過程,則輸出本身的 ID. // 此後其餘線程執行 test_and_set 是 if 語句判斷爲 false, // 所以不會輸出自身 ID. if (!winner.test_and_set()) { std::cout << "thread #" << id << " won!\n"; } }; int main() { std::vector<std::thread> threads; std::cout << "spawning 10 threads that count to 1 million...\n"; for (int i = 1; i <= 10; ++i) threads.push_back(std::thread(count1m, i)); ready = true; for (auto & th:threads) th.join(); return 0; }
屢次執行結果以下:app
atomic ) ./Atomic-Flag1 spawning 10 threads that count to 1 million... thread #6 won! atomic ) ./Atomic-Flag1 spawning 10 threads that count to 1 million... thread #1 won! atomic ) ./Atomic-Flag1 spawning 10 threads that count to 1 million... thread #5 won! atomic ) ./Atomic-Flag1 spawning 10 threads that count to 1 million... thread #1 won! atomic ) ./Atomic-Flag1 spawning 10 threads that count to 1 million... thread #1 won! atomic ) ./Atomic-Flag1 spawning 10 threads that count to 1 million... thread #10 won!
std::atomic_flag 的 test_and_set 函數原型以下:異步
bool test_and_set (memory_order sync = memory_order_seq_cst) volatile noexcept; bool test_and_set (memory_order sync = memory_order_seq_cst) noexcept;
test_and_set() 函數檢查 std::atomic_flag 標誌,若是 std::atomic_flag 以前沒有被設置過,則設置 std::atomic_flag 的標誌,並返回先前該 std::atomic_flag 對象是否被設置過,若是以前 std::atomic_flag 對象已被設置,則返回 true,不然返回 false。異步編程
test-and-set 操做是原子的(所以 test-and-set 是原子 read-modify-write (RMW)操做)。
test_and_set 能夠指定 Memory Order(後續的文章會詳細介紹 C++11 的 Memory Order,此處爲了完整性列出 test_and_set 參數 sync 的取值),取值以下:
Memory Order 值 | Memory Order 類型 |
---|---|
memory_order_relaxed | Relaxed |
memory_order_consume | Consume |
memory_order_acquire | Acquire |
memory_order_release | Release |
memory_order_acq_rel | Acquire/Release |
memory_order_seq_cst | Sequentially consistent |
一個簡單的例子:
#include <iostream> // std::cout #include <atomic> // std::atomic_flag #include <thread> // std::thread #include <vector> // std::vector #include <sstream> // std::stringstream std::atomic_flag lock_stream = ATOMIC_FLAG_INIT; std::stringstream stream; void append_number(int x) { while (lock_stream.test_and_set()) { } stream << "thread #" << x << '\n'; lock_stream.clear(); } int main() { std::vector < std::thread > threads; for (int i = 1; i <= 10; ++i) threads.push_back(std::thread(append_number, i)); for (auto & th:threads) th.join(); std::cout << stream.str() << std::endl;; return 0; }
執行結果以下:
thread #1 thread #2 thread #3 thread #4 thread #5 thread #6 thread #7 thread #8 thread #9 thread #10
清除 std::atomic_flag 對象的標誌位,即設置 atomic_flag 的值爲 false。clear 函數原型以下:
void clear (memory_order sync = memory_order_seq_cst) volatile noexcept; void clear (memory_order sync = memory_order_seq_cst) noexcept;
清除 std::atomic_flag 標誌使得下一次調用 std::atomic_flag::test_and_set 返回 false。
std::atomic_flag::clear() 能夠指定 Memory Order(後續的文章會詳細介紹 C++11 的 Memory Order,此處爲了完整性列出 clear 參數 sync 的取值),取值以下:
Memory Order 值 | Memory Order 類型 |
---|---|
memory_order_relaxed | Relaxed |
memory_order_consume | Consume |
memory_order_acquire | Acquire |
memory_order_release | Release |
memory_order_acq_rel | Acquire/Release |
memory_order_seq_cst | Sequentially consistent |
結合 std::atomic_flag::test_and_set() 和 std::atomic_flag::clear(),std::atomic_flag 對象能夠看成一個簡單的自旋鎖使用,請看下例:
#include <thread> #include <vector> #include <iostream> #include <atomic> std::atomic_flag lock = ATOMIC_FLAG_INIT; void f(int n) { for (int cnt = 0; cnt < 100; ++cnt) { while (lock.test_and_set(std::memory_order_acquire)) // acquire lock ; // spin std::cout << "Output from thread " << n << '\n'; lock.clear(std::memory_order_release); // release lock } } int main() { std::vector<std::thread> v; for (int n = 0; n < 10; ++n) { v.emplace_back(f, n); } for (auto& t : v) { t.join(); } }
在上面的程序中,std::atomic_flag 對象 lock 的上鎖操做能夠理解爲 lock.test_and_set(std::memory_order_acquire); (此處指定了 Memory Order,更多有關 Memory Order 的概念,我會在後續的文章中介紹),解鎖操做至關與 lock.clear(std::memory_order_release)。
在上鎖的時候,若是 lock.test_and_set 返回 false,則表示上鎖成功(此時 while 不會進入自旋狀態),由於此前 lock 的標誌位爲 false(即沒有線程對 lock 進行上鎖操做),但調用 test_and_set 後 lock 的標誌位爲 true,說明某一線程已經成功得到了 lock 鎖。
若是在該線程解鎖(即調用 lock.clear(std::memory_order_release)) 以前,另一個線程也調用 lock.test_and_set(std::memory_order_acquire) 試圖得到鎖,則 test_and_set(std::memory_order_acquire) 返回 true,則 while 進入自旋狀態。若是得到鎖的線程解鎖(即調用了 lock.clear(std::memory_order_release))以後,某個線程試圖調用 lock.test_and_set(std::memory_order_acquire) 而且返回 false,則 while 不會進入自旋,此時代表該線程成功地得到了鎖。
按照上面的分析,咱們知道在某種狀況下 std::atomic_flag 對象能夠看成一個簡單的自旋鎖使用。