【C++多线程】std::atomic<T>

面向的问题

  对简单临界资源的访问,如果使用mutex开销较大。

  如有两个线程,对一个变量进行操作,一个线程读这个变量的值,一个线程往这个变量中写值。即使是一个简单变量的读取和写入操作,如果不加锁,也有可能会导致读写值混乱(一条语句可能会被拆成3、4条汇编语句来执行,所以仍然有可能混乱)

 1 #include <iostream>
 2 #include <thread>
 3 using namespace std;
 4 int g_count = 0;
 5  
 6 void mythread1() {
 7     for (int i = 0; i < 1000000; i++) {
 8         g_count++;
 9     }
10 }
11  
12 int main() {
13     std::thread t1(mythread1);
14     std::thread t2(mythread1);
15     t1.join();
16     t2.join();
17     cout << "正常情况下结果应该是200 0000次,实际是" << g_count << endl;
18 }

   使用std::mutex来解决上述对临界资源访问的问题。结果正常,但是每一次循环都要加锁解锁是的程序开销很大。

 

 1 #include <iostream>
 2 #include <thread>
 3 #include <mutex>
 4 using namespace std;
 5 int g_count = 0;
 6 std::mutex mymutex;
 7 
 8 void mythread1() {
 9     for (int i = 0; i < 1000000; i++) {
10         std::unique_lock<std::mutex> u1(mymutex);
11         g_count++;
12     }
13 }
14  
15  
16 int main() {
17     std::thread t1(mythread1);
18     std::thread t2(mythread1);
19     t1.join();
20     t2.join();
21     cout << "正常情况下结果应该是200 0000次,实际是" << g_count << endl;
22 }

std::atomic<T>

  std::atomic<T>包含在头文件<atomic>中。可以把原子操作理解成一种:不需要用到互斥量加锁(无锁)技术的多线程并发编程方式。原子操作:在多线程中不会被打断的程序执行片段。从效率上来说,原子操作要比互斥量的方式效率要高。互斥量的加锁一般是针对一个代码段,而原子操作针对的一般都是一个变量。原子操作,一般都是指“不可分割的操作”;也就是说这种操作状态要么是完成的,要么是没完成的,不可能出现半完成状态。std::atomic来代表原子操作,是个类模板。其实std::atomic是用来封装某个类型的值。

 1 #include <iostream>
 2 #include <thread>
 3 #include <atomic>
 4 using namespace std;
 5 std::atomic<int> g_count = 0; //封装了一个类型为int的 对象(值)
 6 
 7 void mythread1() {
 8     for (int i = 0; i < 1000000; i++) {
 9         g_count++;
10     }
11 }
12  
13 int main() {
14     std::thread t1(mythread1);
15     std::thread t2(mythread1);
16     t1.join();
17     t2.join();
18     cout << "正常情况下结果应该是200 0000次,实际是" << g_count << endl;
19 }

 

 一般atomic原子操作,针对++,–,+=,-=,&=,|=,^=是支持的,其他操作不一定支持。如下使用g_count = g_count + 1就会产生错误。

 1 #include <iostream>
 2 #include <thread>
 3 #include <atomic>
 4 using namespace std;
 5 std::atomic<int> g_count = 0; //封装了一个类型为int的 对象(值)
 6  
 7 void mythread1() {
 8     for (int i = 0; i < 1000000; i++) {
 9          //虽然g_count使用了原子操作模板,但是这种写法既读又写,
10          //会导致计数错误
11              g_count = g_count + 1;
12     }
13 }
14 
15 int main() {
16     std::thread t1(mythread1);
17     std::thread t2(mythread1);
18     t1.join();
19     t2.join();
20     cout << "正常情况下结果应该是200 0000次,实际是" << g_count << endl;
21 }

  其他需要注意的地方

1 std::atomic<int> atm = 0;
2  
3 cout << atm << endl;

  这里只有读取atm是原子操作,但是整个这一行代码 cout << atm << endl; 并不是原子操作,导致最终显示在屏幕上的值是一个“曾经值”。

1 std::atomic<int> atm = 0;
2  
3 auto atm2 = atm; //不可以

  这种拷贝初始化不可以,会报错。

atomic<int> atm2(atm.load());

  load():以原子方式读atomic对象的值。

atm2.store(12);

  store():以原子方式写。

参考

https://blog.csdn.net/qq_38231713/article/details/106093115

posted @ 2020-07-06 12:42  Chen沉尘  阅读(1808)  评论(0编辑  收藏  举报