一 原子操作概念引出范例
#include <iostream>
#include <thread>
using namespace std;
int g_count = 0; // 定义一个全局量,供两个线程同时访问
void mythread() // 线程入口函数
{
for (int i = 0; i < 10000000; i++)
{
g_count++;
}
}
int main()
{
std::thread mytask1(mythread);
std::thread mytask2(mythread);
mytask1.join();
mytask2.join();
cout << "两个线程执行完毕,最终g_count的值是:" << g_count << endl;
getchar();
return 0;
}
输出结果:
12171204 // 运行第一次结果
12125050 // 运行第一次结果
14455315 // 运行第一次结果
我们原本想要输出结果为2000 0000,但是因为两个线程竞争,导致输出结果不对。
修改如下:我们可以用互斥量来保护共享数据。
std::mutex g_mutex;
void mythread() // 线程入口函数
{
for (int i = 0; i < 10000000; i++)
{
g_mutex.lock();
g_count++;
g_mutex.unlock();
}
}
我们可以用互斥量来保护共享数据,但是由于锁的粒度,频繁的加锁解锁。输出结果的时间耗费了5-6秒的时间。那有什么办法可以提高老运行效率吗?这时就可以用上原子操作了。
#include <iostream>
#include <thread>
#include <atomic>
using namespace std;
atomic<int> g_count = 0; // 定义一个全局量,供两个线程同时访问
void mythread() // 线程入口函数
{
for (int i = 0; i < 10000000; i++)
{
g_count++;
}
}
int main()
{
std::thread mytask1(mythread);
std::thread mytask2(mythread);
mytask1.join();
mytask2.join();
cout << "两个线程执行完毕,最终g_count的值是:" << g_count << endl;
getchar();
return 0;
}
耗时省了一半,用了2-3秒。
什么是原子操作?
原子操作是指“不可分割的操作”,可以看成是一种多线程无锁编程技术。atomic是一个类模板,参数是变量的类型。通过将变量声明成原子变量,则可以保证,在多线程中,每当访问原子变量时,它的值是最新的值。它同互斥量一样,都保护了数据。但是互斥量保护的是一个代码段,可以保护更多的操作。而原子变量是保护一个变量被多个线程同时访问时,这个变量是最新的值。而不会因为线程间不同步导致结果出错。
二 原子操作注意事项
一般原子操作对{ ++, --, +=, &=, ^=, |= … }这些运算符是支持的,其它的可能不支持。
void mythread() // 线程入口函数
{
for (int i = 0; i < 10000000; i++)
{
g_count++; // 结果正确
//g_count += 1; // 结果正确
//g_count = g_count + 1; // 结果不对
}
}
std::atomic不支持拷贝赋值运算
std::atomic<int> atm2 = atm; // 错误,atomic不支持赋值赋值运算, 但是atm2 = 3 可以。
std::atomic<int> atm2(atm.load()); // 正确,以原子方式读atm的值
atm.store(2); // 正确,以原子方式写atm的值
cout << atm << endl;
// 读atm的值是原子操作,但这整句不是原子操作,它要将atm值写入到输出流中,不能保证缓存中都是最新的atm的值。