C++ 標準庫提供了原子操作,(我已經懶得寫序言了)
====================================
先來說原子操作的概念:
原子操作是多執行緒當中對資源進行保護的一種手段,主要作用是和互斥量(Mutex)一樣,避免對資源的并發訪問、修改,
互斥量的粒度衡量是作用域(哪怕作用域內只有一個變數),而原子的粒度衡量則是以一個變數或物件為單位,因此,原子相對于互斥量更加高效,但并非替代關系,
互斥量的主要作用是保護作用域內的資源,而原子的作用是保護一個變數或物件,
因此,當你需要保護的資源僅僅是某個變數或物件時,應首先考慮使用原子,
1,std::atomic
頭檔案:
#include <atomic>
std::atomic 是一個模板類,它的語法是:
std::atomic<Type> name(default_value);
如果你并不明白 std::atomic (原子) 的作用,請看以下代碼及執行結果:
#include <iostream> #include <atomic> #include <thread> int basic_value(0); void ThreadChangeValue() { for (size_t i = 0; i < 1000000; i++) { basic_value++; } return; } std::atomic<int> atomic_int_value(0); void ThreadChangeAtomic() { for (size_t i = 0; i < 1000000; i++) { atomic_int_value.fetch_add(1, std::memory_order_relaxed); } return; }; int main() { std::thread t1(ThreadChangeValue); std::thread t2(ThreadChangeValue); std::thread t3(ThreadChangeAtomic); std::thread t4(ThreadChangeAtomic); t1.join(); t2.join(); t3.join(); t4.join(); std::cout << "Basic Value: " << basic_value << std::endl; std::cout << "Atomic Value: " << atomic_int_value << std::endl; return EXIT_SUCCESS; }
執行結果:
Basic Value: 1123299
Atomic Value: 2000000
以上代碼分別定義了兩個 int 變數,一個是普通的變數,一個是原子變數,兩個變數分別用兩個執行緒去遞增1000000次,
理論上,兩個變數最終值應同為2000000,然而,普通變數卻出現了資源競爭性錯誤,兩個執行緒都有接近一半的操作都是失敗的,導致最終值僅為1123299,
而受原子保護的變數,兩個執行緒的操作則全部成功,
std::atomic 的成員函式表:
| 名稱 | 作用 | 適用記憶體序 |
| operator= | 多載等 | |
| operator T | 從原子物件加載值 | |
| store | 用另一個非原子值替換當前原子化的值 物件型別必須和原子物件宣告時一致 |
memory_order_relaxed memory_order_release memory_order_seq_cst |
| load | 從原子物件當中加載值(回傳) |
memory_order_relaxed memory_order_consume memory_order_acquire memory_order_seq_cst |
| is_lock_free | 檢查原子物件的鎖定狀態 | |
| wait 【std20】 | 阻塞執行緒至被提醒且原子值更改 | |
| exchange | 用另一個原子值替換當前原子值 并回傳先前的原子值 |
memory_order_relaxed memory_order_consume memory_order_acquire memory_order_release memory_order_acq_rel memory_order_seq_cst |
| compare_exchange_weak |
原子地比較原子物件與非原子引數的值,若相等則進行交換,若不相等則進行加載 (允許少部分不符合條件的值回傳) |
memory_order_relaxed memory_order_consume memory_order_acquire memory_order_release memory_order_acq_rel memory_order_seq_cst |
| compare_exchange_strong | 原子地比較原子物件與非原子引數的值,若相等則進行交換,若不相等則進行加載 |
memory_order_relaxed memory_order_consume memory_order_acquire memory_order_release memory_order_acq_rel memory_order_seq_cst |
| notify_one【std20】 | 通知至少一個在該原子物件等待執行緒 | |
| notify_all【std20】 | 通知所有在該原子物件等待執行緒 | |
| [常量] is_always_lock_free | 指示該型別是否始終免鎖 |
除此之外 std::atomic 還對 int 及指標型別做了特殊化增強,以下操作函式僅適用于 int 及指標型別操作:
額外備注:C++ 20 后部分特化支持 float ,
| 名稱 | 作用 | 適用特化型別 | 適用記憶體序 |
| fetch_add | 原子地將引數加到存盤于原子物件的值,并回傳先前保有的值 | int && ptr && float(std20) |
memory_order_relaxed memory_order_consume memory_order_acquire memory_order_release memory_order_acq_rel memory_order_seq_cst |
| fetch_sub | 原子地從存盤于原子物件的值減去引數,并獲得先前保有的值 | int && ptr && float(std20) |
memory_order_relaxed memory_order_consume memory_order_acquire memory_order_release memory_order_acq_rel memory_order_seq_cst |
| fetch_and | 原子地進行引數和原子物件的值的逐位與,并獲得先前保有的值 | int |
memory_order_relaxed memory_order_consume memory_order_acquire memory_order_release memory_order_acq_rel memory_order_seq_cst |
| fetch_or | 原子地進行引數和原子物件的值的逐位或,并獲得先前保有的值 | int |
memory_order_relaxed memory_order_consume memory_order_acquire memory_order_release memory_order_acq_rel memory_order_seq_cst |
| fetch_xor | 原子地進行引數和原子物件的值的逐位異或,并獲得先前保有的值 | int |
memory_order_relaxed memory_order_consume memory_order_acquire memory_order_release memory_order_acq_rel memory_order_seq_cst |
| operator++ | 原子值遞增 | int && ptr | |
| operator-- | 原子值遞減 | int && ptr | |
| operator+= | 原子值增加 | int && ptr && float(std20) | |
| operator-= | 原子值減少 | int && ptr && float(std20) | |
| operator&= | 進行原子按位與 | int | |
| operator|= | 進行原子按位或 | int | |
| operator^= | 進行原子按位異或 | int |
額外補充 std::atomic_flag :
std::atomic_flag 是原子的最基本布爾型別,它是無鎖的,并且它沒有拷貝建構式,也不提供 load 和 store 操作,主要用于提供比 std::atomic 更簡單基本化布爾操作效率,
構造語法:
std::atomic<bool> name(false); std::atomic_flag name = ATOMIC_FLAG_INIT;
成員函式表:
| 名稱 | 作用 |
| operator= | 多載等 |
| clear | 將布林值設定為 false |
| test_and_set | 將布林值設定為 true 并回傳先前值 |
| test【std20】 | 原子的回傳當前值 |
| wait | 阻塞執行緒至被提醒且原子值更改 |
| notify_one【std20】 | 通知至少一個在該原子物件等待執行緒 |
| notify_all【std20】 | 通知所有在該原子物件等待執行緒 |
2,std::memory_order
std::memory_order指定記憶體訪問,包括常規的非原子記憶體訪問,如何圍繞原子操作排序,在沒有任何制約的多處理器系統上,多個執行緒同時讀或寫數個變數時,一個執行緒能觀測到變數值更改的順序不同于另一個執行緒寫它們的順序,其實,更改的順序甚至能在多個讀取執行緒間相異,一些類似的效果還能在單處理器系統上出現,因為記憶體模型允許編譯器變換,庫中所有原子操作的默認行為提供序列一致順序(見后述討論),該默認行為可能有損性能,不過可以給予庫的原子操作額外的
std::memory_order引數,以指定附加制約,在原子性外,編譯器和處理器還必須強制該操作,-- 《C++ Reference》
要理解記憶體序是做什么的,要先從硬體講起:(盡量簡單通俗)
以一顆 CPU i7-10875H 為例,它有8顆物理內核,從物理上來講,它可以同時處理8條并行執行緒,通過超執行緒技術可以擴展到16條執行緒(物理上還是8條),
再在軟體層面來講,并行的數千條執行緒是邏輯并行,終究都要交給 CPU 進行串行處理,而 CPU 可以同時處理的執行緒數量,就是由內核數量決定的,
而每個 CPU 內核所運算資料的存取,并不是直接存取到記憶體當中,而是要先經過每個內核互相獨立的 L1、L2 兩級高速快取,再到 CPU 內核之間共享的 L3 高速快取,再然后到記憶體,
這樣就造成了一個問題,就是,假設一個內核負責的一條執行緒修改了某個變數的值,但是還沒有重繪到內核之間共享的 L3 快取或者記憶體之中,那么這時候其他 CPU 內核從記憶體中讀取到的該變數就仍然是舊值,
所以,為了避免這種情況,這就是 std::memory_order 的作用,
首先,要明白 std::memory_order 本身是什么,它是定義于 <atomic> 頭檔案當中的六個列舉值,使用時用做引數傳遞給 std::atomic 相關的操作函式,比如 load、store 等,
支持傳 std::memory_order 列舉的相關操作函式上文都已經列出,這里重點將這六個列舉都代表什么,
std::memory_order 列舉值說明:
| 名稱 | 作用 |
| memory_order_relexed | 只保證原子值不被其他執行緒同時訪問,但沒有執行緒之間同步、順序制約,其他執行緒可能讀取到記憶體當中的舊值, |
| memory_order_consume |
[C++17注:目前不建議使用]有順序的加載操作,只影響到當前執行緒, 作用是保證之后的load操作不會排在宣告該列舉值的當前load操作之前, |
| memory_order_acquire | 有順序的加載操作,作用是保證之后所有執行緒的load操作不會排在宣告該列舉值的當前load操作之前, |
| memory_order_release | 有順序的釋放操作,作用是保證之后的 load(讀)、store(寫) 性質操作不會排在傳入該列舉值的操作函式之前, |
| memory_order_acq_rel |
有順序整合加載(memory_order_acquire)->釋放(memory_order_release)操作, 當前執行緒的所有 load(讀)、store(寫) 性質操作不會排在傳入該列舉值的操作函式之前后, 所有帶有釋放(memory_order_release)操作同一原子物件的執行緒會排在傳入該列舉值的操作函式之前, 而且當前執行緒對原子值的修改會同步給其他進行讀操作的同一原子物件的執行緒, |
| memory_order_seq_cst |
傳入該列舉值的操作函式,load(讀) 時會進行 memory_order_acquire 操作,store(寫)時會進行 memory_order_release 操作, 如果是讀+寫就是 memory_order_acq_rel 操作, 備注:此列舉值為支持傳入 std::memory_order 操作函式的預設值, |
以下代碼演示了一些最簡單的使用:
下例演示兩個執行緒間傳遞性的釋放獲得順序:
#include <iostream> #include <atomic> #include <thread> #include <string> std::atomic<std::string*> atom_str(nullptr); int flag = 0; void Producer() { std::string* str = new std::string("Hello Byte"); flag = 1; atom_str.store(str, std::memory_order_release); return; } void Consumer() { std::string* str; while (!(str = atom_str.load(std::memory_order_acquire))); if (flag != 1) { // 絕不會執行 std::cout << "Error..." << std::endl; } else { std::cout << str->c_str() << std::endl; } return; } int main() { std::thread t1(Producer); std::thread t2(Consumer); t1.join(); t2.join(); if (atom_str.load() != nullptr) { delete atom_str.load(); } return EXIT_SUCCESS; }
下例演示三個執行緒間傳遞性的釋放獲得順序:
#include <iostream> #include <atomic> #include <thread> #include <vector> std::vector<int> data; std::atomic<int> flag(0); void Producer() { data.push_back(42); flag.store(1, std::memory_order_release); return; } void Broker() { int expected = 1; while (!flag.compare_exchange_strong(expected, 2, std::memory_order_acq_rel)) { expected = 1; } }; void Consumer() { while (flag.load(std::memory_order_acquire) < 2); if (data[0] != 42) { // 絕不會執行 std::cout << "False..." << std::endl; } else { std::cout << "True..." << std::endl; } return; } int main() { std::thread t1(Producer); std::thread t2(Broker); std::thread t3(Consumer); t1.join(); t2.join(); t3.join(); return EXIT_SUCCESS; }
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/493007.html
標籤:其他
