对简单临界资源的访问,如果使用mutex开销较大。
如有两个线程,对一个变量进行操作,一个线程读这个变量的值,一个线程往这个变量中写值。即使是一个简单变量的读取和写入操作,如果不加锁,也有可能会导致读写值混乱(一条语句可能会被拆成3、4条汇编语句来执行,所以仍然有可能混乱)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| #include <iostream> #include <thread> #include <chrono>
using namespace std; using namespace std::chrono; int g_count = 0;
void mythread() { for (int i = 0; i < 1000000; i++) { g_count++; } }
int main() { std::thread t1(mythread); std::thread t2(mythread); auto start = steady_clock::now(); t1.join(); t2.join(); auto end = steady_clock::now(); auto tt = duration_cast<milliseconds>(end - start); cout << "程序用时=" << tt.count() << "毫秒" << endl; cout << "正常情况下结果应该是200 0000次,实际是" << g_count << endl; return 0; }
/* 结果: 程序用时=16毫秒 正常情况下结果应该是200 0000次,实际是1303733 */
|
使用std::mutex来解决上述对临界资源访问的问题。结果正常,但是每一次循环都要加锁解锁是的程序开销很大。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| #include <iostream> #include <thread> #include <chrono> #include <mutex> using namespace std; using namespace std::chrono; int g_count = 0; std::mutex mymutex;
void mythread() { for (int i = 0; i < 1000000; i++) { std::unique_lock<std::mutex> u1(mymutex); g_count++; } }
int main() { std::thread t1(mythread); std::thread t2(mythread); auto start = steady_clock::now(); t1.join(); t2.join(); auto end = steady_clock::now(); auto tt = duration_cast<milliseconds>(end - start); cout << "程序用时=" << tt.count() << "毫秒" << endl; cout << "正常情况下结果应该是200 0000次,实际是" << g_count << endl; return 0; }
/* 结果: 程序用时=349毫秒 正常情况下结果应该是200 0000次,实际是2000000 */
|
std::atomic
std::atomic包含在头文件中。可以把原子操作理解成一种:不需要用到互斥量加锁(无锁)技术的多线程并发编程方式。原子操作:在多线程中不会被打断的程序执行片段。从效率上来说,原子操作要比互斥量的方式效率要高。互斥量的加锁一般是针对一个代码段,而原子操作针对的一般都是一个变量。原子操作,一般都是指“不可分割的操作”;也就是说这种操作状态要么是完成的,要么是没完成的,不可能出现半完成状态。std::atomic来代表原子操作,是个类模板。其实std::atomic是用来封装某个类型的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| #include <iostream> #include <thread> #include <atomic> #include <chrono>
using namespace std; using namespace std::chrono; std::atomic<int> g_count = 0; //封装了一个类型为int的 对象(值)
void mythread() { for (int i = 0; i < 1000000; i++) { g_count++; } }
int main() { std::thread t1(mythread); std::thread t2(mythread); auto start = steady_clock::now();
t1.join(); t2.join(); auto end = steady_clock::now(); auto tt = duration_cast<milliseconds>(end - start); cout << "程序用时=" << tt.count() << "毫秒" << endl; cout << "正常情况下结果应该是200 0000次,实际是" << g_count << endl; return 0; }
/* 程序用时=51毫秒 正常情况下结果应该是200 0000次,实际是2000000 */
|
一般atomic原子操作,针对++,–,+=,-=,&=,|=,^=是支持的,其他操作不一定支持。如下使用g_count = g_count + 1就会产生错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #include <iostream> #include <thread> #include <atomic> using namespace std; std::atomic<int> g_count = 0; //封装了一个类型为int的 对象(值)
void mythread1() { for (int i = 0; i < 1000000; i++) { //虽然g_count使用了原子操作模板,但是这种写法既读又写, //会导致计数错误 g_count = g_count + 1; } }
int main() { std::thread t1(mythread1); std::thread t2(mythread1); t1.join(); t2.join(); cout << "正常情况下结果应该是200 0000次,实际是" << g_count << endl; }
|
其他需要注意的地方
1 2
| std::atomic<int> atm = 0; cout << atm << endl;
|
这里只有读取atm是原子操作,但是整个这一行代码 cout << atm << endl; 并不是原子操作,导致最终显示在屏幕上的值是一个“曾经值”。
1 2
| std::atomic<int> atm = 0; auto atm2 = atm; //不可以
|
这种拷贝初始化不可以,会报错。
1
| atomic<int> atm2(atm.load());
|
load():以原子方式读atomic对象的值。
store():以原子方式写。
参考:
https://www.cnblogs.com/chen-cs/p/13254219.html