volatile关键字总结

先说结论

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(system_clock::now() - start).count() << endl;

代码很简单,就是把变量i自增5000万次,最后计算这个过程用的毫秒数

debug模式下,打印结果如下:

volatile关键字总结_第1张图片

release模式下,打印结果如下:

volatile关键字总结_第2张图片

可以看到,同样的代码,

debug模式下耗时15毫秒

release模式下竟然耗时0毫秒,也就是程序根本就没执行5000万次自增,因为编译器发现5000万次自增后面压根没用到变量i,它认为这些操作毫无意义,然后自作主张地跳过了5000万次自增对应的编译命令,所以耗时0毫秒

实验二:volatile并不能完全禁用编译器的指令重排

实验代码如下:

int a = 0;
int b = 0;
void foo(void)
{
    a = b + 1;
    b = 2;
}

debug模式下,我们在代码"b = 2"这一行,打上断点,并打开反汇编窗口查看对应的汇编指令,可以看到如下图

volatile关键字总结_第3张图片

"a = b + 1" 对应3个汇编指令

"b = 2" 对应1个汇编指令

 各个汇编指令完全符合我们的预期

改为release模式,我们打开反汇编查看对应更大汇编指令,如下图

volatile关键字总结_第4张图片

会发现

第二句代码"b = 2"对应的汇编指令居然在前面

第一句代码"a = b+1"对应的汇编指令反而在后面,且经过了变形

也就是说,编译器帮我们进行了指令优化和重排

实验二 - 优化1 - 给一个变量加上volatile关键字

我们给变量a加上volatile关键字,代码如下

volatile int a = 0;
int b = 0;
void foo(void)
{
    a = b + 1;
    b = 2;
}

release模式下,再次查看反汇编,如下图

volatile关键字总结_第5张图片

可以看到,给变量a使用volatile关键字修饰后,编译器虽然强制从内存取了一次数据,但仍然进行了指令优化,比如直接把1赋给了变量a,然后才对变量b的寄存器执行xor操作,仍然不是最初的汇编指令。

实验二 - 优化二 - 给两个变量都加上volatile关键字

我们给变量a,变量b都加上volatile,代码如下

volatile int a = 0;
volatile int b = 0;
void foo(void)
{
    a = b + 1;
    b = 2;
}

在release模式下,再查看反汇编窗口,如下图,总算符合我们的预期了

volatile关键字总结_第6张图片

可以看到,现在总算正常了,几乎跟debug版的汇编指令一模一样

代码"a = b + 1",对应了4条汇编指令

代码"b = 2",对应了1条汇编指令

结合本例和上面的优化一,证明了:

单个volatile变量并不能完全阻止编译器对指令进行重排优化,两个volataile共同作用才能阻止编译器对两个volatile变量之间的汇编指令进行重排优化

注意:虽然两个volatile变量可以限制编译器层面的指令重排,但是cpu还是有可能出现指令重排,因为cpu为了提高执行速度,不会严格按照编译后的汇编指令进行执行,它同样会进行一定的优化,以至于最终结果和我们设想的不一致,请参考我的这篇博客

cpu的指令重排,禁用办法-CSDN博客

你可能感兴趣的:(知识积累,volatile,volatile关键字,c++的volatile)