文章目錄
本文介紹 頭文件中最簡單的原子類型: atomic_flag。atomic_flag 一種簡單的原子布爾類型,只支持兩種操作,test-and-set 和 clear。
std::atomic_flag 構造函數
std::atomic_flag 構造函數如下:
- atomic_flag() noexcept = default;
- atomic_flag (const atomic_flag&T) = delete;
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;
}
多次執行結果如下:
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 介紹
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::clear() 介紹
清除 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 不會進入自旋,此時表明該線程成功地獲得了鎖。
Other refereces
C++11 併發指南( < atomic > 類型詳解二 std::atomic )
C++11 併發指南(atomic 類型詳解三 std::atomic (續))
C++11 併發指南(atomic 類型詳解四 C 風格原子操作介紹)