先说结论
1. volatile关键字可以让编译器层面减少优化,每次使用时必须从内存中取数据,而不是从cpu缓存或寄存器中获取
2. volatile关键字不能完全禁止指令重排,准确地说是两个volatile修饰的变量之间的命令不会进行指令重排
3. 使用volatile可以解决一部分的线程并发问题,但是不能解决所有的并发问题
4. volatile只能让编译器不做指令重排的优化,但是cpu层面的指令重排仍然不受影响。如果想禁止cpu的指令重排,可以使用__sync_synchronize()
实验一:volatile修饰的变量会让编译器减少优化,每次使用时必须从内存中取数据
代码如下:
const auto start = system_clock::now();
int i = 0;
while (i<50000000)
{
i++;
}
cout << duration_cast<milliseconds>(system_clock::now() - start).count() << endl;
代码很简单,就是把变量i自增5000万次,计算这个过程用的毫秒数
debug模式下,打印结果如下:
release模式下,打印结果如下:
可以看到,同样的代码,
debug模式下耗时15毫秒
release模式下竟然耗时0毫秒,也就是程序根本就没执行5000万次自增,因为编译器发现5000万次自增后面压根没用到变量i,这些操作等于是无意义的,所以自作主张地跳过了5000万次自增对应的编译命令
实验二:volatile并不能完全禁用编译器的指令重排
代码如下:
int a = 0;
int b = 0;
void foo(void)
{
a = b + 1;
b = 2;
}
在debug模式下,我们打上断点,并打开反汇编窗口查看对应的汇编指令,可以看到如下图
a = b + 1对应3个汇编指令
b = 2 对应1个汇编指令
完全符合我们的预期
改为release模式,我们打开反汇编查看对应更大汇编指令,如下图
可以发现
第二句代码"b=2"的汇编指令居然在前面
第一句代码"a = b+1"的汇编指令反而在后面,且经过了变形
说明,编译器帮我们进行了优化
实验二 - 优化一
我们给变量a加上volatile,如下
volatile int a = 0;
int b = 0;
void foo(void)
{
a = b + 1;
b = 2;
}
release模式下,再次查看反汇编,如下图
可以看到,给变量a使用volatile关键字修饰后,编译器虽然强制从内存取了一次数据,但仍然进行了指令优化,比如先把1赋给了变量a,然后才对变量b的寄存器执行xor操作,仍然不是最初的汇编指令。
实验二 - 优化二
我们给变量a,变量b都加上volatile,如下
volatile int a = 0;
volatile int b = 0;
void foo(void)
{
a = b + 1;
b = 2;
}
在release模式下,再查看反汇编窗口,如下图,总算符合我们的预期了
说明,单个volatile并不能完全阻止编译器对指令进行重排优化,两个volataile共同作用才能阻止编译器对指令进行重排优化