本文简单介绍volatile关键字的使用,进而引出编译期间内存乱序的问题,并介绍了有效防止编译器内存乱序所带来的问题的解决方法,文中简单提了下CPU指令乱序的现象,但并没有深入讨论。

以下是我搭建的博客地址: http://itblogs.ga/blog/20150329150706/    欢迎到这里阅读文章。

volatile关键字

volatile关键字用来修饰一个变量,提示编译器这个变量的值随时会改变。通常会在多线程、信号处理、中断处理、读取硬件寄存器等场合使用。

程序在执行时,通常将数据(变量的值)从内存的读到寄存器中,然后进行运算,此后对该变量的处理,都是直接访问寄存器就可以了,不再访问内存,因为 访存的代价是很高的(这块是访问寄存器还是重新访存加载到寄存器是编译器在编译阶段就决定了的)。但在上述说的几种情况下,内存会被另一个线程或者信号处 理函数、中断处理函数、硬件改掉,这样,代码只访问寄存器的话,永远得不到真实的值。

对这样的变量(会在多线程、线程与信号、线程与中断处理中共同访问的,或者硬件寄存器),在定义时都会加上volatile关键字修饰。这样编译器 在编译时,编译出的指令会重新访存,这样就能保证拿到正确的数据了。但这里需要注意的是,编译器只能做到让指令重新访问内存,而不是直接使用寄存器中的 值,这些和缓存没有关系,具体执行时指令是访问内存还是访问的缓存,编译器也无法干预。

另外,除了使用寄存器来避免多次访存外,编译器有时可能直接将变量全部优化掉,使用常数代替。比如:

int main()
{
    int a = 1;
    int b = 2;

printf("a = %d, b = %d \n", a, b);
}

   

编译器可能直接优化为:

int main()
{
    printf("a = %d, b = %d \n", 1, 2);
}

   

如果对ab的声明加了 volatile关键字,编译器将不在做这样的优化。

还有,对所有volatile变量,编译器在编译阶段保证不会将访问volatile变量的指令进行乱序重排。

指令乱序

那么什么是指令乱序,指令乱序是为了提高性能,而导致的执行时的指令顺序和代码写的顺序不一致。指令乱序有编译期间指令乱序和执行时指令乱序。

执行时指令乱序是CPU的一个特性,这块比较复杂,不再这里提及。我们只需要知道在x86/x64的体系架构下,程序员一般不需要关注执行时指令乱序(不需要关注不代表没有)。

编译期间指令乱序是指在编译成二进制代码时,编译器为了所谓的优化进行了指令重排,导致二进制指令的顺序和我们写的代码的顺序是不一致的。

比如以下代码:

int a;
int b;

int main()
{
    a = b + 1;
    b = 0;
}

会被优化成(实际上在汇编阶段进行的乱序优化,优化后的代码也只能以汇编的方式查看,这里只是拿C代码举例说明一下):

int a;
int b;

int main()
{
    b = 0;
    a = b + 1;
}

对加上volatile关键字的变量的访问,编译器不会进行指令乱序的优化,保证volatile变量的访问顺序和代码写的是一样的。比如如下代码不会优化:

volatile int a;
volatile int b;

int main()
{
    a = b + 1;
    b = 0;
}

   

但是以下代码,依然会乱序,因为编译器只是保证volatile变量访问的顺序,对于非volatile变量之间,以及volatile以及非volatile变量之间的顺序,编译器还是会优化。

int a;
volatile int b;

int main()
{
    a = b + 1;
    b = 0;
}

   

asm volatile ("" : : : "memory");

一般编程时如果使用到volatile关键字,那么基本上都需要考虑编译器指令乱序的问题。解决编译器指令乱序所带来的问题,除了上面将必要的变量声明为volatile,还可以使用下面一条嵌入式汇编语句:

1 asm volatile ("" : : : "memory");

这是一条空汇编语句,只是告诉编译器,内存发生了变化。编译器遇到这条语句后,会生成访存更新寄存器的指令,将所有的寄存器的值更新一遍。这里是编译器遇到这条语句额外生成了一些代码,而不是CPU遇到这条语句执行了一些处理,因为这条语句本身并没有CPU指令与之对应。

由于编译器知道这条语句之后内存发生了变化,编译器在编译时就会保证这条语句上下的指令不会乱,即这条语句上面的指令,不会乱序到语句下面,语句下面的指令不会乱序到语句上面。

利用编译器这个功能,程序员可以:

1、利用这条语句,强制程序访存,而不是使用寄存器中的值,作为使用volatile关键字的一个替代手段;

2、在不允许乱序的两个语句之间插入这条语句从而保证不会被编译器乱序。

下面看一个应用的例子,两个线程访问共享的全局变量:

#define ARRAY_LEN 12

volatile int flag = 0;
int a[ARRAY_LEN];

pthread1()
{
    a[ARRAY_LEN - 1] = 10; <br>    asm volatile ("" : : : "memory");
    flag = 1;
}

pthread2()
{
    int sum = 0;

if(flag == 0) {
        sum += a[ARRAY_LEN - 1];
    }    
}线程2假定flag==1时,线程1已经将数据放到数组中了。但实际上,如果没有  asm volatile ("" : : : "memory"),线程1并不能保证flag = 1在数组赋值之后。原因就是我们前面提到的编译器指令乱序。

指令乱序是一个比较复杂的话题,我们这里只考虑了编译器指令乱序,在intel架构的CPU上,基本上考虑到这些就足够了。但在弱指令序的CPU上,比如mips,了解这些还远远不够。本文不打算展开CPU指令乱序的话题,感兴趣的可以参考以下文章了解以下:

   
   

volatile关键字的使用

volatile关键字使用和const一致,下面是一个总结:

char const * pContent;       // *pContent是const,   pContent可变
(char *) const pContent;     //  pContent是const,  *pContent可变
char* const pContent;        //  pContent是const,  *pContent可变
char const* const pContent;  //  pContent 和       *pContent都是const

   

沿着*号划一条线,如果const位于*的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量;如果const位于*的右侧,const就是修饰指针本身,即指针本身是常量。

参考资料

Memory Ordering at Compile Time

以下是我搭建的博客地址:
原文链接:http://itblogs.ga/blog/20150329150706/ 转载请注明出处

volatile关键字及编译器指令乱序总结的更多相关文章

  1. 【操作系统之十一】任务队列、CPU Load、指令乱序、指令屏障

    一.CPU Loadcpu load是对使用或者等待cpu进程的统计(数量的累加):每一个使用(running)或者等待(runnable)CPU的进程,都会使load值+1;每一个结束的进程,都会使 ...

  2. 【转】volatile关键字。编译器不优化,多线程会改。防止随时变动的

    来自:http://www.cnblogs.com/yc_sunniwell/archive/2010/07/14/1777432.html 1. 为什么用volatile? C/C++ 中的 vol ...

  3. 【Java虚拟机5】Java内存模型(硬件层面的并发优化基础知识--指令乱序问题)

    前言 其实之前大家都了解过volatile,它的第一个作用是保证内存可见,第二个作用是禁止指令重排序.今天系统学习下为什么CPU会指令重排. 存储器的层次结构图 1.CPU乱序执行指令的根源 CPU读 ...

  4. memory barrier 内存屏障 编译器导致的乱序

    小结: 1. 很多时候,编译器和 CPU 引起内存乱序访问不会带来什么问题,但一些特殊情况下,程序逻辑的正确性依赖于内存访问顺序,这时候内存乱序访问会带来逻辑上的错误, 2. https://gith ...

  5. volatile 关键字的复习

    今天早上看何登成的微博(http://hedengcheng.com/?p=725) 对volatile 关键字语意进行了深入分析. 看完之后,用自己的话总结如下: 1.c/c++ volatile中 ...

  6. C# 基础回顾: volatile 关键字

    有些人可能从来没看到过这个关键字,这也难怪,因为这个关键字并不常用.那这个关键字到底有什么用呢? 我在网上搜索这个关键字的时候,发现很多朋友都有一个错误的认识 ------ 认为这个关键字可以防止并发 ...

  7. Java并发编程:volatile关键字解析

    Java并发编程:volatile关键字解析 volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在 ...

  8. 【转】Java并发编程:volatile关键字解析

    转自:http://www.importnew.com/18126.html#comment-487304 volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备 ...

  9. (转)Java并发编程:volatile关键字解析

    转:http://www.cnblogs.com/dolphin0520/p/3920373.html Java并发编程:volatile关键字解析 volatile这个关键字可能很多朋友都听说过,或 ...

随机推荐

  1. 页面Button/Link 传参数

    很多情况下,我们需要在一个标准的页面上添加一个button 或者 是Link, 在点击的过程中想把,一些参数传值到另外一个自定义的页面: 下面这个例子说明是如何操作的 如下图所示,是创建另一个Obje ...

  2. 以小时候玩的贪吃蛇为例,对于Java图像界面的学习感悟

    简介 正文 01.JFrame是啥? 02.JPanel 03. KeyListener 04.Runnable 05.游戏Running 06.游戏初始类编写 07.main 简介: 一直以来用代码 ...

  3. div嵌套导致子区域margin-top失效不起作用的解决方法

    有两个嵌套关系的div,如果外层div的父元素padding值为0, 那么内层div的margin-top或者margin-bottom的值会"转移"给外层div,使父元素产生上外 ...

  4. sharepoint 2013 开发环境安装

    Sharepoint 介绍 Sharepoint 可以帮助企业用户轻松完成日常工作中诸如文档审批.在线申请等业务流程,同时提供多种接口实现后台业务系统的集成,它将 Office 桌面端应用的优势结合 ...

  5. 复旦大学2015--2016学年第一学期高等代数I期末考试情况分析

    一.期末考试成绩班级前几名 胡晓波(93).宋沛颖(92).张舒帆(91).姚人天(90).曾奕博(90).杨彦婷(90).白睿(88).唐指朝(87).谢灵尧(87).蔡雪(87) 二.总成绩计算方 ...

  6. 一个简单的loading,纯属自娱自乐

    /// <reference path="/scripts/js/jquery.min.js" /> var zsw = { loading: function (im ...

  7. nRF52832开发日志--SAADC调试

    今天各种事情比较多.......技术活时间略少,就搞了这一项~ 52832的ADC和之前51822系列还是有蛮大差别的: 1.支持差分输入方式,测量结果为两输入端口电压差的转换的有符号数值,这个功能对 ...

  8. 我为开源做贡献,网页正文提取——Html2Article

    为什么要做正文提取 一般做舆情分析,都会涉及到网页正文内容提取.对于分析而言,有价值的信息是正文部分,大多数情况下,为了便于分析,需要将网页中和正文不相干的部分给剔除.可以说正文提取的好坏,直接影响了 ...

  9. 《与小卡特一起学Python》 Code2

    下边是一个猜数字的小游戏: 几乎所有语言都这样做的…… here we go! import random secret = random.randint(1,99) guess = 0 tries ...

  10. Linux多线程与同步

    作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 典型的UNIX系统都支持一个进程创建多个线程(thread).在Linux进程基础 ...