在多線程開發中,爲了確保數據安全性,經常需要對數據進行加鎖、解鎖處理。C++11中引入了原子的概念,簡而言之就是訪問它時它自動加鎖解鎖,從而使軟件開發更爲簡便。
原子可謂一個既簡單又複雜的概念。簡單到訪問它時就跟單線程訪問一塊內存一樣簡單,複雜的地方在於它的實現涉及到各種內存模型,在優化中經常會遇到。
下面給出一個簡單的原子示例:
#include <iostream>
#include <thread>
#include <atomic>
using namespace std;
atomic_int val = { 0 };//這個類型也可以寫作 atomic<int> 用於表示整型數據的原子
void icrement () {
for (int i = 0; i < 100000000; i++) {
val++;
}
}
int main (int argc, char* argv []) {
//創建兩個線程
thread t1 (icrement);
thread t2 (icrement);
//等待兩個線程執行完
t1.join ();
t2.join ();
cout << val << endl;
return 0;
}
經過十幾秒左右的等待後,代碼執行完畢,結果不出所料,200000000。簡單的原子操作差不多就是這樣,atomic模板可以包括任何類型,另外原子的操作也與它本身的操作方式基本相同,因爲原子模板重載了所有的運算符。簡單的說完了,說說複雜的原子概念。
假如一個原子,它長這樣
atomic_int val = { 0 };
嗯,跟上面的相同。它實際上可以提供三種類型的操作:讀、寫、RMW(同時包括讀寫),通過三種類型的函數實現。首先是讀,比如 int i=val; 這樣的代碼,實際上是通過load函數實現。
int i = val.load (memory_order_seq_cst);
後面的參數代表內存順序。這個的含義是順序執行當前的原子操作。什麼含義?含義就是,如果一個函數中對這個原子進行了多項操作,那麼首先執行之前的原子操作,然後執行本條操作,最後執行之後的原子操作。說白了就是單線程的執行順序。原子的操作過程並不是必須固定的,一個函數中如果有兩條原子操作,那麼首先執行後面操作,然後執行前面操作是完全可能的。這個在優化中經常會遇到。然後是寫操作,比如 val = i; 這樣的操作
val.store (i, memory_order_seq_cst);
嗯,這兒也順序執行,以免顛覆各位三觀。
然後就是同時讀寫這樣的操作了。 比如原子+=一個數之後同時可訪問,通過compare_exchange這類函數實現。
然後,接下來說說內存訪問模型了。一共有六種
1、memory_order_seq_cst 順序執行,可用於讀、寫、RMW操作
2、memory_order_relaxed 亂序執行,可用於讀、寫、RMW操作
3、memory_order_acq_rel 首先執行之前的寫操作,然後執行本條操作,然後執行之後的讀操作,可用於RMW操作
4、memory_order_release 首先執行之前的寫操作,然後執行本條操作,可用於寫、RMW操作
5、memory_order_acquire 首先執行本條操作,然後執行後面的讀操作,可用於讀、RMW操作
6、memory_order_consume 首先執行本條操作,然後執行後面的讀寫操作,可用於讀、RMW操作
基本的概念就是上面這些了,接下來動手實踐吧