目录
1、原子操作std::atomic相关概念
原子操作:更小的代码片段,并且该片段必定是连续执行的,不可分割。
1.1 原子操作std::atomic与互斥量的区别
1)互斥量:类模板,保护一段共享代码段,可以是一段代码,也可以是一个变量。
2)原子操作std::atomic:类模板,保护一个变量。
1.2 为何需要原子操作std::atomic
为何已经有互斥量了,还要引入std::atomic呢,这是因为互斥量保护的数据范围比较大,我们期望更小范围的保护。并且当共享数据为一个变量时,原子操作std::atomic效率更高。
2、不加锁情况
#include <thread>
#include <atomic>
#include <iostream>
#include <time.h>
using namespace std;
// 全局的结果数据
long total = 0;
// 点击函数
void click()
{
for (int i = 0; i < 10000000; ++i)
{
// 对全局数据进行无锁访问
total += 1;
}
}
int main(int argc, char* argv[])
{
// 计时开始
clock_t start = clock();
// 创建3个线程模拟点击统计
thread th1(click);
thread th2(click);
thread th3(click);
th1.join();
th2.join();
th3.join();
// 计时结束
clock_t finish = clock();
// 输出结果
cout << "result:" << total << endl;
cout << "duration:" << finish - start << "ms" << endl;
return 0;
}
执行结果如下:从执行的结果来看,这样的方法虽然非常快,但是结果不正确
3、加锁情况
#include <thread>
#include <atomic>
#include <iostream>
#include <time.h>
#include <mutex>
using namespace std;
// 全局的结果数据
long total = 0;
mutex m;
// 点击函数
void click()
{
for (int i = 0; i < 10000000; ++i)
{
//加锁
m.lock();
// 对全局数据进行无锁访问
total += 1;
//解锁
m.unlock();
}
}
执行结果:互斥对象的使用,保证了同一时刻只有唯一的一个线程对这个共享进行访问,从执行的结果来看,互斥对象保证了结果的正确性,但是也有非常大的性能损失,从刚才的313ms变成了现在的3858,用了原来时间的10多倍的时间。这个损失够大。
4、原子操作
#include <thread>
#include <atomic>
#include <iostream>
#include <time.h>
using namespace std;
// 全局的结果数据
atomic<long> total = 0;
// 点击函数
void click()
{
for (int i = 0; i < 10000000; ++i)
{
// 对全局数据进行无锁访问
total += 1;
}
}
结果正确!耗时只是使用mutex互斥对象的六分之一!也仅仅是不采用任何保护机制的时间的近2倍。可以说这是一个非常不错的成绩了
5、总结
原子操作的实现跟普通数据类型类似,但是它能够在保证结果正确的前提下,提供比mutex等锁机制更好的性能,如果我们要访问的共享资源可以用原子数据类型表示,那么在多线程程序中使用这种新的等价数据类型,是一个不错的选择。