详解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; 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