詳解c++多線程(四)
C++中的原子操作
一、atomic模版函數
為了避免多個線程同時修改全局變量,C++11除了提供互斥量mutex這種方法以外,還提供了atomic模版函數。
使用atomic可以避免使用鎖,而且更加底層,比mutex效率更高。
為了方便使用,c++11為模版函數提供了別名。
atomic<bool> 別名:atomic_bool
atomic<int> 別名:atomic_int
atomic<char> 別名:atomic_char
atomic<long> 別名:atomic_long
我們先來看一個例子:
#include <thread> #include <iostream> #include <vector> #include <atomic> using namespace std; void func(int& counter) { for (int i = 0; i < 100000; ++i) { ++counter; } } int main() { //atomic<int> counter(0); int counter = 0; //新建一個整型原子counter,將counter初始化為0 vector<thread> threads; for (int i = 0; i < 10; ++i) { threads.push_back(thread(func, ref(counter))); } for (auto& current_thread : threads) { current_thread.join(); } cout << "Result = " << counter << ‘\n‘; return 0; }
輸出結果:
顯然這個結果不是我們想要的,多跑幾次就會發現,每一次的結果都會不一樣。而這段代碼的問題就在於多個線程同時修改了counter這個數導致出現錯誤。
了解了前幾章以後知道了鎖可以用來解決這個問題,但是其實原子類型可以更加方便得解決這個問題。
只需要把counter的原來的int型,改為atomic_int型就可以了,非常方便,也不需要用到鎖。
#include <thread> #include <iostream> #include <vector> #include <atomic> using namespace std; void func(atomic_int& counter) { for (int i = 0; i < 100000; ++i) { ++counter; } } int main() { //atomic<int> counter(0); atomic_int counter(0); //新建一個整型原子counter,將counter初始化為0 vector<thread> threads; for (int i = 0; i < 10; ++i) { threads.push_back(thread(func, ref(counter))); } for (auto& current_thread : threads) { current_thread.join(); } cout << "Result = " << counter << ‘\n‘; return 0; }
輸出結果:
結果就正確了。
二、std::atomic_flag
std::atomic_flag是一個原子型的布爾變量,只有兩個操作:
1)test_and_set,如果atomic_flag 對象已經被設置了,就返回True,如果未被設置,就設置之然後返回False
2)clear,把atomic_flag對象清掉
註意這個所謂atomic_flag對象其實就是當前的線程。如果當前的線程被設置成原子型,那麽等價於上鎖的操作,對變量擁有唯一的修改權。
調用clear就是類似於解鎖。
來看一個例子:
#include <iostream> #include <atomic> #include <vector> #include <thread> #include <sstream> std::atomic_flag lock = ATOMIC_FLAG_INIT; //初始化原子flag std::stringstream stream; void append_number(int x) { while(lock.test_and_set()); //如果原子flag未設置,那麽返回False,就繼續後面的代碼。否則一直返回True,就一直停留在這個循環。 stream<<"thread#" <<x<<‘\n‘; lock.clear(); //去除flag的對象 } int main() { std::vector<std::thread> threads; for(int i=0;i<10;i++) threads.push_back(std::thread(append_number, i)); for(auto& th:threads) th.join(); std::cout<<stream.str()<<‘\n‘; }
再看一個例子:
#include <iostream> #include <atomic> #include <vector> #include <thread> #include <sstream> using namespace std; atomic<bool> ready(false); atomic_flag winner = ATOMIC_FLAG_INIT; void count1m(int id) { while(!ready) //如果ready=false,就會讓當前線程一直在等待狀態 this_thread::yield(); //此時ready為true for(int i=0;i<1000;i++);//數數 //當有某個線程結束計數,然後被flag設置成了原子線程則返回false,於是執行打印id語句 //由於並沒有clear,所以該線程會一直是原子線程,而其他線程調用test_and_set就會一直返回True,於是不會執行後面的打印語句 if(!winner.test_and_set()) cout<<"winner thread id = "<<id<<endl; } int main() { vector<thread> threads; for(int i=0;i<10;i++) threads.push_back(thread(count1m, i)); ready = true; for(auto &th:threads) th.join(); }
參考:
https://blog.csdn.net/yhc166188/article/details/80572108
https://www.cnblogs.com/taiyang-li/p/5914331.html
詳解c++多線程(四)