关键字volatile
简述
volatile关键字修饰变量,函数或者对象时,可以避免程序因优化导致的错误。为什么程序会优化程序呢?又会出现什么错误呢?下面我们用一个例子来看一下。
例子
//定义变量
int index = 0;
// 线程1
for(int i = 0;i < 100;i++)
{
index++;
}
//线程2
for(int i = 0;i < 100;i++)
{
index++;
}
这个就是简单的两个线程对一个变量进行改变(按理说需要加锁来避免资源冲突,但是很多时候为了提高效率并不喜欢加锁),期望结果是200,但是各位可以测试一下,结果往往并不是我们期望的值,100~200各种各样的值可能都会出现。为什么会这样呢?下面通过汇编语言角度来分析下这个问题。
当index定义后,其值是放在了内存区,在编译器进行编译时会将index++转为3部分
- 将值从内存拷贝到寄存器中
- 将寄存器中值进行+1操作
- 最后将寄存器的值拷贝到内存中完成对内存值的覆盖
编译器在对一个线程内的for循环编译时,就变成了100次的从内存拷贝,改变值,覆盖内存值操作。聪明的你是不是感觉这太费事了,我直接拷贝1次,改变100次的值,最后将改变的值在拷贝回去不久行了吗?原来需要300次机器指令完成的事情,102次指令就给完成了,速度提高了可不是一星半点。编译器在优化时也是这样想的,但是具体没有这么极端,可能计算10次就将计算结果覆盖内存的值,并从内存再一次获得值,具体多少次我这里也不清楚。这样就会出现一个问题,如果一个人来干这件事,那么没问题,如果很多大聪明都这么干就有问题了。比如第一个线程A从内存中取走的时候是100,自己加了5次之后,将105准备拷贝回去,由于时间片被另一个线程B给抢走,他从内存中去取值的时候发现也是100,自己也赶紧加5次,然后准备拷贝回去的时候,时间片再次给了A,A将内存中的100替换成了105,A取走105再次进行计算,时间片再次给到了B,B 将内存中的105替换成了105,B取走105再次计算,然后重复直至各自循环结束。从结果来看,最终的结果就会各种各样了。
C++语言人员也发现了这种问题,但是呢普通的循环该优化还是要优化,这种问题呢也需要解决,于是volatile这个关键字就诞生了。C++语言和程序员约定,如果程序员编写的代码有这个关键字修饰,C++语言编译的时候内就不再省略拷贝覆盖过程,老老实实的按照拷贝计算覆盖执行。但是作为程序员的你要是不加修饰,嘿嘿,那就某怪老子优化的错,出了问题就是你自己编码问题。除了多线程外,多任务(进程)和中断时也会有这种现象。当然笨办法就是加锁,让各个线程并行的强行给变为串行。锁如果加的不好,执行效率肯定会大打折扣。