浅谈volatile关键字与内存栅栏(barrier)

<1>volatile

volatile的本意是“易变的” 因为访问寄存器要比访问内存单元快的多,所以编译器一般都会作减少存取内存的优化,但有可能会读脏数据。当要求使用volatile声明变量值的时候,系统总是重新从它所在的内存读取数据, 即使它前面的指令刚刚从该处读取过数据。

精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器中的值从而可以提供对特殊地址的稳定访问;如果不使用valatile,则编译器将对所声明的语句进行优化。(简洁的说就是:volatile关键词影响编译器编译的结果,用 volatile声明的变量表示该变量随时可能发生变化,与该变量有关的运算,不要进行编译优化,以免出错)

举例说明一下,在我们写代码中,经常需要等待某个事件的触发,所以经常会写出这样的程序:

short flag;
void test()
{
    do1();
    while(flag==0);
    do2();
}

    这段程序等待内存变量flag的值变为1之后才运行do2()。如果变量flag的值由别的程序更改,那么这个程序可能是某个硬件中断服务程序。例如:如果某个按钮按下的话,就会对程序产生中断,在按键中断程序中修改flag为1,这样上面的程序就能够得以继续运行。但是,由于编译器判断在test函数里面没有修改过flag(即编译器并不知道flag的值会被别的程序修改),因此在它进行优化的时候,只执行一次对从flag到某寄存器的读操作,然后每次判断都只使用这个寄存器里面的“flag副本”,等待这个寄存器里面的副本值变为1。如果不幸进行了这样的优化,那么while循环就变成了死循环,因为寄存器的内容不可能被中断服务程序修改。为了让程序每次都读取真正flag变量的值,就需要定义为如下形式:
volatile short flag;
    需要注意的是,没有volatile也可能能正常运行,但是可能修改了编译器的优化级别之后就又不能正常运行了。因此经常会出现debug版本正常,但是release版本却不能正常的问题。所以为了安全起见,只要是等待别的程序修改某个变量的话,就加上volatile关键字。

  一般说来,volatile用在如下的几个地方:
1、中断服务程序中修改的供其它程序检测的变量需要加volatile;
2、多任务环境下各任务间共享的标志应该加volatile;
3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;

  即volatile关键字的两个功能:
(1)防止编译器对汇编指令做顺序上的优化。
(2)防止寄存器存储变量的副本值。 应用在多线程中 

volatile关键字指导道编译器自动的为读写字段加屏障,以下是MSDN的解析:

volatile关键字指示一个字段可以由多个同时执行的线程修改.声明为volatile的字段不受编译器优化(假定由单个线程访问)的限制.这样可以确保该字段在任何时间呈现的都是最新的值.

使用volatile字段可以被总结成下表:

注意到应用volatile关键字,并不能保证写后面跟读的操作不被交换.这就可能会造成莫名其妙的问题.例如:

扫描二维码关注公众号,回复: 2684712 查看本文章

  volatile int x, y;

        void Test1()

        {

            x = 1; //volatile write

            int a = y;//volatile read

        }

        void Test2()

        {

            y = 1;//volatile write

            int b = x;//volatile read

        }

这是Test1和Test2在不同的线程中并发执行,有可能a和b字段的值都是0(尽管在x和y上应用了volatile关键字).这段代码的意思是说,即使使用了volatile,也无法保证操作的顺序不被交换.volatile关键字可以确保线程读取到最新的值,但保证不了操作顺序.

如果我们需要保证CPU不对指令顺序进行调优,我们就必须用到内存栅栏了(barrier)

<2>barrier

内存访问速度远不及CPU处理速度,为提高机器整体性能,在硬件上引入硬件高速缓存Cache,加速对内存的访问。另外在现代CPU中指令的执行并不一定严格按照顺序执行,没有相关性的指令可以乱序执行(即对指令进行顺序上的调优),以充分利用CPU的指令流水线,达到指令级并行,提高指令执行的速度。但是如果程序员不希望发生顺序的调优,可以在可能发生调优的上下程序段之间添加barrier(),以防止CPU或者编译器对指令的调优。

<3>一个参数既可以是const还可以是volatile吗?

这个是当然可以的,我们用const定义的变量其目的在于本程序不能更改,但是volatile是通知编译器变量可能会被外部因素(例如有可能是多线程中其他线程改动了,因为变量共享,虽然你在本线程中不能更改,但是在其他线程中可以修改)改变 所以程序需要每次都必须从内存中读取,而不是直接用缓存里的。

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

猜你喜欢

转载自blog.csdn.net/Eunice_fan1207/article/details/81565563
今日推荐