艾丽游戏ing

voliate关键字 voliate关键字的作用

艾丽游戏ing 1

voliate关键字是什么?

对于语句Load1;StoreStore;Store2;Load1和Store2语句不允许重排序。

volatile影响编译器编译的结果,指出,volatile变量是随时可能发生变化的,与volatile变量有关的运算,不要进行编译优化,以免出错。

voliate关键字 voliate关键字的作用voliate关键字 voliate关键字的作用


对于语句Store1;StoreStore;Store2;Store1和Store2语句不允许重排序。

这里存放了变量目前的“准确值”。每个线程可以有它自己的变量拷贝,而这个变量拷贝值可以和“主”内存区域里存放的不同。因此实际上存在一种可能:“主”内存区域里的i1值是1,线程1里的i1值是2。

编译器优化:

应用程序之所以复杂, 是由于它们具有处理多种问题以及相关数据集的能力。实际上, 一个复杂的应用程序就象许多不同功能的应用程序“ 粘贴” 在一起。源文件中大部分复杂性来自于处理初始化和问题设置代码。这些文件虽然通常占源文件的很大一部分, 具有很大难度, 但基本上不花费C PU 执行周期。

尽管存在上述情况, 大多数Makefile文件只有一套编译器选项来编译项目中所有的文件。因此, 标准的优化方法只是简单地提升优化选项的强度, 一般从O 2 到O 3。这样一来, 就需要投人大量 精力来调试, 以确定哪些文件不能被优化, 并为这些文件建立特殊的make规则。

voliate关键字是什么?

程序执行10次输出的结果如下:

volatile影响编译器编译的结果,指出,volatile变量是随时可能发生变化的,与volatile变量有关的运算,不要进行编译优化,以免出错。

也就是到CPU的底层执行的命令其实就是这个lock,这个lock指令既完成了变量的可见性还保证了禁止指令充排序:

这里存放了变量目前的“准确值”。每个线程可以有它自己的变量拷贝,而这个变量拷贝值可以和“主”内存区域里存放的不同。因此实际上存在一种可能:“主”内存区域里的i1值是1,线程1里的i1值是2。

编译器优化:

应用程序之所以复杂, 是由于它们具有处理多种问题以及相关数据集的能力。实际上, 一个复杂的应用程序就象许多不同功能的应用程序“ 粘贴” 在一起。源文件中大部分复杂性来自于处理初始化和问题设置代码。这些文件虽然通常占源文件的很大一部分, 具有很大难度, 但基本上不花费C PU 执行周期。

尽管存在上述情况, 大多数Makefile文件只有一套编译器选项来编译项目中所有的文件。因此, 标准的优化方法只是简单地提升优化选项的强度, 一般从O 2 到O 3。这样一来, 就需要投人大量 精力来调试, 以确定哪些文件不能被优化, 并为这些文件建立特殊的make规则。

voliate怎么保证可见性

上次我们学习了volatile是如何解决多线程环境下共享变量的内存可见性}}问题,并且简单介绍了基于多核CPU并发缓存架构模型的Ja内存模型。

详情见文章:

volatile很难?由浅入深怼到CPU汇编,搞清楚它的底层原理

在并发编程中,有三个重要的特性:

内存可见性

原子性

有序性

volatile解决了并发编程中的可见性和有序性,解决不了原子性的问题,原子性的问题需要依赖synchronized关键字来解决。

关于并发编程三大特性的详细介绍,大家可以点击下方卡片搜索查看:

搜更多精彩内容

并发编程三大特性

内存可见性在上一篇文章中已经验证,本文我们继续通过代码学习如下内容:1、volatile为什么解决不了原子性问题?2、缓存行、缓存行填充3、CPU优化导致的乱序执行4、经典面试题:DCL必须要有volatile关键字吗?5、valatile关键字是如何禁止指令重排序的?

以上每一步都会有一段代码来验证,话不多说,开始输出干货!

1、volatile为什么解决不了原子性问题?

如果你对volatile了解的还可以,那么咱们继续往下看,如果不是太熟悉,请先行阅读上一篇文章。

老规矩,先来一段代码:

volatile原子性问题代码验证

这段代码的输出结果是多少?10000?大于10000?小于10000?

join:10000

join:10000

join:10000

join:9819

join:10000

join:10000

join:10000

join:9898

join:10000

join:10000

还是用上篇文章的图来说明一下程序的整体流程:

当线程1从内存读取num的值到工作内存,同时线程2也从内存读取num的值到工作内存了,他俩各自作自己的num++作,但是关键点来了:

当线程1、2都执行完num++,线程1执行第五步store作,通过总线将新的num值写回内存,刷新了内存中的num值,同时触发了总线嗅探机制,告知线程2其工作内存中的num不可用,因此线程2的num++得到的值被抛弃了,但是线程2的num++作却是执行了。

2、CPU缓存行

写上篇文章的时候,有朋友问到了CPU的缓存以及缓存行相关的问题,然后我就找了一些资料学习,形成了下面的一张图:

CPU缓存行

CPU和主内存RAM之间会有缓存,因为CPU的速度要比内存的速度要快的多,大概是100:1,也就是CPU的速度比内存要快100倍,因此有了CPU缓存,那为什么是缓存呢?不是四级、五级呢?四个大字送给你:工业实践!相关概念:

ALU:CPU计算单元,加减乘除都在这里算

PC:寄存器,ALU从寄存器读取一次数据为一个周期,需要时间小于1ns

L1:1级缓存,当ALU从寄存器拿不到数据的时候,会从L1缓存去拿,耗时约1ns

L2:2级缓存,当L1缓存里没有数据的时候,会从L2缓存去拿,耗时约3ns

L3:3级缓存,一颗CPU里的双核共用,L2没有,则去L3去拿,耗时约15ns

缓存行:CPU从内存读取数据到缓存行的时候,是一行一行的缓存,每行是64字节(现代处理器)

问题来了:

1、缓存行存在的意义?好处是什么?

空间的考虑:一个地址被访问,相连的地址很大可能也被访问;

时间的考虑:最近访问的会被频繁访问好处:比如相连的地址,典型的就是数组,连续内存访问,很快!

2、缓存行会带来什么问题?

缓存行会导致缓存失效的问题,从而导致程序运行效率低下。例如下图:

当x,y两个变量在一个缓存行的时候:

1、线程1执行x++作,将x和y所在的缓存行缓存到cpu core1里面去,

3、线程1执行了x++作,写入到内存,同时为了保证cpu的缓存一致性协议,需要使其他内核x,y所在的缓存行失效,意味着线程2去执行y++作的时候,无法从自己的cpu缓存拿到数据,必须从内存获取。

这就是缓存行失效!

一段代码来验证缓存行失效的问题:

缓存行失效例程

耗时:2079ms

这个时候我们做一个程序的改动,在x变量的前面和后面各加上7个long类型变量,如下:

再次运行,看耗时输出:

耗时:671ms

大约三倍的速度距!

关于缓存行的更多概念,大家也可以点击下方卡片直接搜索更多信息:

搜更多精彩内容

缓存行

3、CPU优化导致的乱序执行

上文在说缓存行的时候,主要是因为CPU的速度大约是内存的速度的100倍,因此CPU在执行指令的时候,为了不等待内存数据的读取,会存在CPU指令优化而导致乱序执行的情况。

看下面这段代码:

cpu乱序执行例程

执行后输出(我执行了900多万次才遇到x=0,y=0的情况,可以试试你的运气哦~):

因为CPU的速度比内存要快100倍,所以当有两行不相关的代码在执行的时候,CPU为了优化执行速度,是会乱序执行的,所以上面的程序会输出:x=0,y=0的情况,也就是两个线程的执行顺序变成了:

x = b;

y = a;

a = 1;

b = 1;

这个时候我们就需要加volatile关键字了,来禁止CPU的指令重排序!

4、DCL单例模式需要加volatile吗?

一道经典的面试题:DCL单例模式需要加volatile字段吗?先来看DCL单例模式的一段代码:

DCL单例模式

DCL全称叫做Double Check Lock,就是双重检查锁来保证一个对象是单例的。

核心的问题就是这个INSTANCE变量是否需要加volatile关键字修饰?肯定是需要的。

首先我们来看new一个对象的字节码指令:

查看其字节码指令:

NEW ja/lang/Object

INVOKESPECIAL ja/lang/Object. ()V

ASTORE 1

即:

1、创建并默认初始化Object对象;

2、作数栈对该对象的引用;

3、调用Object对象的初始化方法;

4、将变量Object o指向创建的这个对象,此时变量o不再为null;

根据上文描述我们知道因为CPU和内存速度不匹配的问题,CPU在执行命令的时候是乱序执行的,即CPU在执行第3步初始化方法时候如果需要很长的时间,CPU是不会等待第3步执行完了才去执行第4步,所以执行顺序可能是1、2、4、3。

那么继续看DCL单例程序,当线程1执行new DCLStudy()的顺序是先astore再invokespecial,但是invokespecial方法还没有执行的时候,线程2进来了,这个时候线程2拿到的就是一个半初始化的对象。

因此,DCL单例模式需要加volatile关键字,来禁止上述new对象的过程的指令重排序!

valatile关键字是如何禁止指令重排序的

JVM规范中规定:凡是被volatile修饰的变量,在进行其作时候,需要加内存屏障!

JVM规范中定义的JSR内存屏障定义:

LoadLoad屏障:

对于语句Load1;LoadLoad;Load2;Load1和Load2语句不允许重排序。

StoreStore屏障:

LoadStore屏障:

StoreLoad屏障:

对于语句Store1;StoreStore;Load2;Store1和Load2语句不允许重排序。

JVM层面volatile的实现要求:

如果对一个volatile修饰的变量进行写作:

前面加StoreStoreBarrier屏障,保证前面所有的store作都执行完了才能对当前volatile修饰的变量进行写作;

后面要加StoreLoadBarrier,保证后面所有的Load作必须等volatile修饰的变量写作完成。

如果对一个volatile修饰的变量进行读作:

后面的读作LoadLoadBarrier必须等当前volatile修饰变量读作完成才能读;

后面的写作LoadStoreBarrier必须等当前的volatile修饰变量读作完成才能写。

上篇文章我们通过一定的方式看到了程序执行的volatile修饰的变量底层汇编码:

0x000000010d3f3203: lock addl $0x0,(%rsp) ;putstatic flag

; - com.ja.study.VolatileStudy::lambda$main$1@9 (line 31)

LOCK用于在多处理器中执行指令时对共享内存的独占使用。它的作用是能够将当前处理器对应缓存的内容刷新到内存,并使其他处理器对应的缓存失效;另外还提供了有序的指令无法越过这个内存屏障的作用。

end

至此,对volatile的学习就到这里了,通过两篇文章来对volatile这个关键字有了一个系统的学习。

学无止境,对volatile的学习还只是一个基础学习,还有更多的知识等待我们去探索学习,例如:

什么是as-if-serial?什么是happens-before?

Ja的哪些指令可以重排序呢?重排序的规则是什么?

我们下期再见!

1372阅读

搜索

volatil2、线程2执行y++作,也将x和y所在的缓存行缓存到cpu core2里面去,e底层原理

汇编111条指令详解

ja必背100源代码

volatile架构图

嵌入式volatile详解

一个参数既可以是const还可以是volatile吗

1.volatile的本意是“易变的” 因为访问寄存器要比访问内存单元快的多,所以编译器一般都会作减少存取内存的优化,但有可能会读数据。当要求使用volatile声明变量值的时候,系统总是重新从它所在的内存读取数据, 即使它前面的指令刚刚从该处读取过数据。地说就是,遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳 定访问;如果不使用valatile,则编译器将对所声明的语句进行优化。(简洁的说就是:volatile影响编译器编译的结果,用 volatile声明的变量表示该变量随时可能发生变化,与该变量有关的运算,不要进行编译优化,以免出错)

2.看两个事例:

1>告诉compiler不能做任何优化

比如要往某一地址送两指令:

int ip =...; //设备地址

ip = 2; //第二个指令

以上程序compiler可能做优化而成:

int ip = ...;

ip = 2;

结果个指令丢失。如果用volatile, compiler就不允许做任何的优化,从而保证程序的原意:

volatile int ip = ...;

ip = 1;

ip = 2;

即使你要compiler做优化,它也不会把两次付值语句间化为一。它只能做其它的优化。

2>用volatile定义的变量会在程序外被改变,每次都必须从内存中读取,而不能重复使用放在cache或寄存器中的备份。

volatile char a;

a=0;

while(!a){

//do some things;

}doother();

如果没有 volatiledoother()不会被执行

3.下面是使用volatile变量的几个场景:

1>中断服务程序中修改的供其它程序检测的变量需要加volatile;

例如:

static 程序执行10次输出的结果如下:int i=0;

int main(void)

{...

while (1){

if (i) dosoming();

/ Interrupt serv routine. /

void ISR_2(void)

{i=1;

}程 序的本意是希望ISR_2中断产生时,在main函数中调用dosoming函数,但是,由于编译器判断在main函数里面没有修改过i,因此可能 只执行一次对从i到某寄存器的读作,然后每次if判断都只使用这个寄存器里面的“i副本”,导致dosoming永远也不会被调用。如果将变量加 上volatile修饰,则编译器保证对此变量的读写作都不会被优化(肯定执行)。此例中i也应该如此说明。

2>多任务环境下各任务间共享的标志应该加volatile

3>存储器映射的硬件寄存器通常也要加voliate,因为每次对它的读写都可能有不同意义。

假设要对一个设备进行初始化,此设备的某一个寄存器为0xff800000。

int output = (unsigned int )0xff800000;//定义一个IO端口;

int init(void)

{int i;

for(i=0;i< 10;i++){

output = i;

}}

经过编译器优化后,编译器认为前面循环半天都是废话,对的结果毫无影响,因为最终只是将output这个指针赋值为9,所以编译器给你编译编译的代码结果相当于:

int init(void)

{output = 9;

}如 果你对此外部设备进行初始化的过程是必须是像上面代码一样顺序的对其赋值,显然优化过程并不能达到目的。反之如果你不是对此端口反复写作,而是反复读 作,其结果是一样的,编译器在优化后,也许你的代码对此地址的读作只做了一次。然而从代码角度看是没有任何问题的。这时候就该使用volatile通知 编译器这个变量是一个不稳定的,在遇到此变量时候不要优化。

volatile int output=(volatile unsigned int )0xff800000;//定义一个I/O端口

另外,以上这几种情况经常还要同时考虑数据的完整性(相互关联的几个标志读了一半被打断了重写),在1中可以通过关中断来实现,2中禁止任务调度,3中则只能依靠会有小几率的出现小于10000的情况,因此volatile是无法保证原子性的,那么到底在什么地方出问题了呢?硬件的良好设计。

4.几个问题

1)一个参数既可以是const还可以是volatile吗?

可以的,例如只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。

2) 一个指针可以是volatile 吗?

可以,当一个中服务子程序修该一个指向一个buffer的指针时。

5.volatile的本质:

1> 编译器的优化

在本次线程内, 当读取一个变量时,为提高存取速度,编译器优化时有时会先把变量读取到一个寄存器中;以后,再取变量值时,就直接从寄存器中取值;当变量值在本线程里改变时,会同时把变量的新值copy到该寄存器中,以便保持一致。

当变量在因别的线程等而改变了值,该寄存器的值不会相应改变,从而造成应用程序读取的值和实际的变量值不一致。

当该寄存器在因别的线程等而改变了值,原变量的值不会改变,从而造成应用程序读取的值和实际的变量值不一致。

2>volatile应该解释为“直接存取原始内存地址”比较合适,“易变的”这种解释简直有点误导人。

6.下面的函数有什么错误:

int square(volatile int ptr)

{return ptr ptr;

}该程序的目的是用来返指针ptr指向值的平方,但是,由于ptr指向一个volatile型参数,编译器将产生类似下面的代码:

int square(volatile int ptr)

{int a,b;

a = ptr;

b = ptr;

return a b;

}由于ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:

long square(volatile int ptr)

{int a;

a = ptr;

return a a;

}注意:频繁地使用volatile很可能会增加代码尺寸和降低性能,因此要合理的使用volatile。

voliate怎么保证可见性

例如:

上次我们学习了volatile是如何解决多线程环境下共享变量的内存可见性问题,并且简单介绍了基于多核CPU并发缓存架构模型的Ja内存模型。

RAM内存:当缓存都没有数据的时候,会从内存读取数据

详情见文章:

volatile很难?由浅入深怼到CPU汇编,搞清楚它的底层原理

在并发编程中,有三个重要的特性:

内存可见性

原子性

有序性

volatile解决了并发编程中的可见性和有序性,解决不了原子性的问题,原子性的问题需要依赖synchronized关键字来解决。

关于并发编程三大特性的详细介绍,大家可以点击下方卡片搜索查看:

搜更多精彩内容

并发编程三大特性

内存可见性在上一篇文章中已经验证,本文我们继续通过代码学习如下内容:1、volatile为什么解决不了原子性问题?2、缓存行、缓存行填充3、CPU优化导致的乱序执行4、经典面试题:DCL必须要有volatile关键字吗?5、valatile关键字是如何禁止指令重排序的?

以上每一步都会有一段代码来验证,话不多说,开始输出干货!

1、volatile为什么解决不了原子性问题?

如果你对volatile了解的还可以,那么咱们继续往下看,如果不是太熟悉,请先行阅读上一篇文章。

老规矩,先来一段代码:

volatile原子性问题代码验证

这段代码的输出结果是多少?10000?大于10000?小于10000?

join:10000

join:10000

join:10000

join:9819

join:10000

join:10000

join:10000

join:9898

join:10000

join:10000

还是用上篇文章的图来说明一下程序的整体流程:

当线程1从内存读取num的值到工作内存,同时线程2也从内存读取num的值到工作内存了,他俩各自作自己的num++作,但是关键点来了:

当线程1、2都执行完num++,线程1执行第五步store作,通过总线将新的num值写回内存,刷新了内存中的num值,同时触发了总线嗅探机制,告知线程2其工作内存中的num不可用,因此线程2的num++得到的值被抛弃了,但是线程2的num++作却是执行了。

2、CPU缓存行

写上篇文章的时候,有朋友问到了CPU的缓存以及缓存行相关的问题,然后我就找了一些资料学习,形成了下面的一张图:

CPU缓存行

CPU和主内存RAM之间会有缓存,因为CPU的速度要比内存的速度要快的多,大概是100:1,也就是CPU的速度比内存要快100倍,因此有了CPU缓存,那为什么是缓存呢?不是四级、五级呢?四个大字送给你:工业实践!相关概念:

ALU:CPU计算单元,加减乘除都在这里算

PC:寄存器,ALU从寄存器读取一次数据为一个周期,需要时间小于1ns

L1:1级缓存,当ALU从寄存器拿不到数据的时候,会从L1缓存去拿,耗时约1ns

L2:2级缓存,当L1缓存里没有数据的时候,会从L2缓存去拿,耗时约3ns

L3:3级缓存,一颗CPU里的双核共用,L2没有,则去L3去拿,耗时约15ns

缓存行:CPU从内存读取数据到缓存行的时候,是一行一行的缓存,每行是64字节(现代处理器)

问题来了:

1、缓存行存在的意义?好处是什么?

空间的考虑:一个地址被访问,相连的地址很大可能也被访问;

时间的考虑:最近访问的会被频繁访问好处:比如相连的地址,典型的就是数组,连续内存访问,很快!

2、缓存行会带来什么问题?

缓存行会导致缓存失效的问题,从而导致程序运行效率低下。例如下图:

当x,y两个变量在一个缓存行的时候:

1、线程1执行x++作,将x和y所在的缓存行缓存到cpu core1里面去,

3、线程1执行了x++作,写入到内存,同时为了保证cpu的缓存一致性协议,需要使其他内核x,y所在的缓存行失效,意味着线程2去执行y++作的时候,无法从自己的cpu缓存拿到数据,必须从内存获取。

这就是缓存行失效!

一段代码来验证缓存行失效的问题:

缓存行失效例程

耗时:2079ms

这个时候我们做一个程序的改动,在x变量的前面和后面各加上7个long类型变量,如下:

再次运行,看耗时输出:

耗时:671ms

大约三倍的速度距!

关于缓存行的更多概念,大家也可以点击下方卡片直接搜索更多信息:

搜更多精彩内容

缓存行

3、CPU优化导致的乱序执行

上文在说缓存行的时候,主要是因为CPU的速度大约是内存的速度的100倍,因此CPU在执行指令的时候,为了不等待内存数据的读取,会存在CPU指令优化而导致乱序执行的情况。

看下面这段代码:

cpu乱序执行例程

执行后输出(我执行了900多万次才遇到x=0,y=0的情况,可以试试你的运气哦~):

因为CPU的速度比内存要快100倍,所以当有两行不相关的代码在执行的时候,CPU为了优化执行速度,是会乱序执行的,所以上面的程序会输出:x=0,y=0的情况,也就是两个线程的执行顺序变成了:

x = b;

y = a;

a = 1;

b = 1;

这个时候我们就需要加volatile关键字了,来禁止CPU的指令重排序!

4、DCL单例模式需要加volatile吗?

一道经典的面试题:DCL单例模式需要加volatile字段吗?先来看DCL单例模式的一段代码:

DCL单例模式

DCL全称叫做Double Check Lock,就是双重检查锁来保证一个对象是单例的。

核心的问题就是这个INSTANCE变量是否需要加volatile关键字修饰?肯定是需要的。

首先我们来看new一个对象的字节码指令:

查看其字节码指令:

NEW ja/lang/Object

INVOKESPECIAL ja/lang/Object. ()V

ASTORE 1

即:

1、创建并默认初始化Object对象;

2、作数栈对该对象的引用;

3、调用Object对象的初始化方法;

4、将变量Object o指向创建的这个对象,此时变量o不再为null;

根据上文描述我们知道因为CPU和内存速度不匹配的问题,CPU在执行命令的时候是乱序执行的,即CPU在执行第3步初始化方法时候如果需要很长的时间,CPU是不会等待第3步执行完了才去执行第4步,所以执行顺序可能是1、2、4、3。

那么继续看DCL单例程序,当线程1执行new DCLStudy()的顺序是先astore再invokespecial,但是invokespecial方法还没有执行的时候,线程2进来了,这个时候线程2拿到的就是一个半初始化的对象。

因此,DCL单例模式需要加volatile关键字,来禁止上述new对象的过程的指令重排序!

valatile关键字是如何禁止指令重排序的

JVM规范中规定:凡是被volatile修饰的变量,在进行其作时候,需要加内存屏障!

JVM规范中定义的JSR内存屏障定义:

LoadLoad屏障:

对于语句Load1;LoadLoad;Load2;Load1和Load2语句不允许重排序。

StoreStore屏障:

LoadStore屏障:

StoreLoad屏障:

对于语句Store1;StoreStore;Load2;Store1和Load2语句不允许重排序。

JVM层面volatile的实现要求:

如果对一个volatile修饰的变量进行写作:

前面加StoreStoreBarrier屏障,保证前面所有的store作都执行完了才能对当前volatile修饰的变量进行写作;

后面要加StoreLoadBarrier,保证后面所有的Load作必须等volatile修饰的变量写作完成。

如果对一个volatile修饰的变量进行读作:

后面的读作LoadLoadBarrier必须等当前volatile修饰变量读作完成才能读;

后面的写作LoadStoreBarrier必须等当前的volatile修饰变量读作完成才能写。

上篇文章我们通过一定的方式看到了程序执行的volatile修饰的变量底层汇编码:

0x000000010d3f3203: lock addl $0x0,(%rsp) ;putstatic flag

; - com.ja.study.VolatileStudy::lambda$main$1@9 (line 31)

LOCK用于在多处理器中执行指令时对共享内存的独占使用。它的作用是能够将当前处理器对应缓存的内容刷新到内存,并使其他处理器对应的缓存失效;另外还提供了有序的指令无法越过这个内存屏障的作用。

end

至此,对volatile的学习就到这里了,通过两篇文章来对volatile这个关键字有了一个系统的学习。

学无止境,对volatile的学习还只是一个基础学习,还有更多的知识等待我们去探索学习,例如:

什么是as-if-serial?什么是happens-before?

Ja的哪些指令可以重排序呢?重排序的规则是什么?

我们下期再见!

1372阅读

搜索

volatile底层原理

汇编111条指令详解

ja必背100源代码

volatile架构图

嵌入式volatile详解

一个参数既可以是const还可以是volatile吗

ip = 1; //个指令

1.volatile的本意是“易变的” 因为访问寄存器要比访问内存单元快的多,所以编译器一般都会作减少存取内存的优化,但有可能会读数据。当要求使用volatile声明变量值的时候,系统总是重新从它所在的内存读取数据, 即使它前面的指令刚刚从该处读取过数据。地说就是,遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳 定访问;如果不使用valatile,则编译器将对所声明的语句进行优化。(简洁的说就是:volatile影响编译器编译的结果,用 volatile声明的变量表示该变量随时可能发生变化,与该变量有关的运算,不要进行编译优化,以免出错)

ja自学一般要学多久

2.看两个事例:

1>告诉compiler不能做任何优化

比如要往某一地址送两指令:

int ip =...; //设备地址

ip = 2; //第二个指令

以上程序compiler可能做优化而成:

int ip = ...;

ip = 2;

结果个指令丢失。如果用volatile, compiler就不允许做任何的优化,从而保证程序的原意:

volatile int ip = ...;

ip = 1;

ip = 2;

即使你要compiler做优化,它也不会把两次付值语句间化为一。它只能做其它的优化。

2>用volatile定义的变量会在程序外被改变,每次都必须从内存中读取,而不能重复使用放在cache或寄存器中的备份。

volatile char a;

a=0;

while(!a){

//do some things;

}doother();

如果没有 volatiledoother()不会被执行

3.下面是使用volatile变量的几个场景:

1>中断服务程序中修改的供其它程序检测的变量需要加volatile;

例如:

static int i=0;

int main(void)

{...

while (1){

if (i) dosoming();

/ Interrupt serv routine. /

void ISR_2(void)

{i=1;

}程 序的本意是希望ISR_2中断产生时,在main函数中调用dosoming函数,但是,由于编译器判断在main函数里面没有修改过i,因此可能 只执行一次对从i到某寄存器的读作,然后每次if判断都只使用这个寄存器里面的“i副本”,导致dosoming永远也不会被调用。如果将变量加 上volatile修饰,则编译器保证对此变量的读写作都不会被优化(肯定执行)。此例中i也应该如此说明。

2>多任务环境下各任务间共享的标志应该加volatile

3>存储器映射的硬件寄存器通常也要加voliate,因为每次对它的读写都可能有不同意义。

假设要对一个设备进行初始化,此设备的某一个寄存器为0xff800000。

int output = (unsigned int )0xff800000;//定义一个IO端口;

int init(void)

{int i;

for(i=0;i< 10;i++){

output = i;

}}

经过编译器优化后,编译器认为前面循环半天都是废话,对的结果毫无影响,因为最终只是将output这个指针赋值为9,所以编译器给你编译编译的代码结果相当于:

int init(void)

{output = 9;

}如 果你对此外部设备进行初始化的过程是必须是像上面代码一样顺序的对其赋值,显然优化过程并不能达到目的。反之如果你不是对此端口反复写作,而是反复读 作,其结果是一样的,编译器在优化后,也许你的代码对此地址的读作只做了一次。然而从代码角度看是没有任何问题的。这时候就该使用volatile通知 编译器这个变量是一个不稳定的,在遇到此变量时候不要优化。

volatile int output=(volatile unsigned int )0xff800000;//定义一个I/O端口

另外,以上这几种情况经常还要同时考虑数据的完整性(相互关联的几个标志读了一半被打断了重写),在1中可以通过关中断来实现,2中禁止任务调度,3中则只能依靠硬件的良好设计。

4.几个问题

1)一个参数既可以是const还可以是volatile吗?

可以的,例如只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。

2) 一个指针可以是volatile 吗?

可以,当一个中服务子程序修该一个指向一个buffer的指针时。

5.volatile的本质:

1> 编译器的优化

在本次线程内, 当读取一个变量时,为提高存取速度,编译器优化时有时会先把变量读取到一个寄存器中;以后,再取变量值时,就直接从寄存器中取值;当变量值在本线程里改变时,会同时把变量的新值copy到该寄存器中,以便保持一致。

当变量在因别的线程等而改变了值,该寄存器的值不会相应改变,从而造成应用程序读取的值和实际的变量值不一致。

当该寄存器在因别的线程等而改变了值,原变量的值不会改变,从而造成应用程序读取的值和实际的变量值不一致。

2>volatile应该解释为“直接存取原始内存地址”比较合适,“易变的”这种解释简直有点误导人。

6.下面的函数有什么错误:

int square(volatile int ptr)

{return ptr ptr;

}该程序的目的是用来返指针ptr指向值的平方,但是,由于ptr指向一个volatile型参数,编译器将产生类似下面的代码:

int square(volatile int ptr)

{int a,b;

a = ptr;

b = ptr;

return a b;

}由于ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:

long square(volatile int ptr)

{int a;

a = ptr;

return a a;

}注意:频繁地使用volatile很可能会增加代码尺寸和降低性能,因此要合理的使用volatile。