【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():以原子方式写。
参考
菜鸟手记。