C++11中,对于多线程访问的变量,可以将其声明为原子类型,之后就不需要为原子数据类型显式地声明互斥锁或调用加锁、解锁的API,线程就能够对原子类型变量进行互斥地访问。
1、原子操作:就是多线程程序中“最小的且不可并行化的”操作。
2、声明原子类型,需要头文件 atomic
3、声明原子类型后,对于其变量的操作将为原子操作
下面的程序中,分别声明全局变量为普通( int )类型和原子类型(atomic_int),然后调用三个线程对此全局变量进行操作,并且输出最终的结果。
#include <iostream>
#include <atomic>
#include <thread>
#include <windows.h>
using namespace std;
// int sum = 0; // 普通类型
atomic_int sum = 0; // 原子类型
void func()
{
for (int i = 0; i < 100; ++i)
{
Sleep(1); // 每次计算前,先等待 1 毫秒,保证三个线程交错对 sum 进行操作
sum += i;
}
}
int main()
{
// 声明三个线程对 sum 进行操作
thread t1(func);
thread t2(func);
thread t3(func);
// 等待所有的操作完成
t1.join();
t2.join();
t3.join();
// 观察 sum 的值,是否因为多个线程同时访问,导致非原子性操作,从而使得计算结果错误
// 如果运行每次的值都不同,则说明非原子性操作导致数值叠加的不正确
// 使用原子类型atomic_*,可保证对变量的访问为原子操作,每次计算的值都是正确的
cout << "sum = " << sum << endl; // 正确结果:14850
return EXIT_SUCCESS;
}
经过执行测试后,发现声明为原子类型(atomic_int)的变量每次执行结果都是正确的,而声明为普通类型的变量,由于非原子性操作,对于变量未进行加锁保护,整个操作过程三个线程交错进行,导致叠加结果产生差异,每次执行的结果都不一致(注:也可能会有执行正确的时候,但是是一个概率问题)。
当将func()中的for循环扩大或增加Sleep()中的时间,问题更为明显。
由此可见,使用原子类型可以很好的适应多线程环境下对于变量的读写操作,而且代码也很简洁,避免了复杂的锁机制。
上述程序只声明了atomic_int的原子类型,其他原子类型及类型支持的操作见下图:
图片来自《深入理解C++11:C++11新特性解析与应用》
至于此特性是否可以广泛应用到实际的工程代码中,而完全抛弃读写锁、互斥量等机制,目前还未经过验证,仍有待证实吧。
不过初次见到此特性的话,真的会产生让人眼前一亮的感觉,尤其是习惯使用读写锁来对共享变量进行保护的操作,看到原子类型是否有种柳暗花明又一村的感觉?
谢谢阅读。