去年年底的样子,何登成写了一篇关于C/C++ volatile关键字的深度剖析blog(C/C++ Volatile关键词深度剖析)。全文深入分析了volatile关键字的三个特性。这里不想就已有内容再做一遍重复,而是再提供一些自己的看法,以完善对volatile的全面认识。

前文一个很好的例子就是:

在这个例子里事实上还引入的另外一个问题,就是多线程环境里该如何使用volatile?

要全面回答这个问题,没那么容易。不过一个已经被很多人接受的结论已经有了,并且很具有权威性。这个结论来自于Linux kernel documention。

C programmers have often taken volatile to mean that the variable could be
changed outside of the current thread of execution; as a result, they are
sometimes tempted to use it in kernel code when shared data structures are
being used
.  In other words, they have been known to treat volatile types
as a sort of easy atomic variable, which they are not
The use of volatile in
kernel code is almost never correct
.

说明白点就是,在linux kernel这种大型并且复杂的系统编程项目里,不能使用volatile,除非能给出强有力的证据!所以我们的项目中,几乎可以肯定,根本没有使用的必要。

结论已经有了,接下来就是阐述为什么了。

在多线程中使用volatile,很多情况下就是为了解决共享数据的访问问题。比方说上面这个例子,如果不使用volatile,那么编译器生成的代码在访问flag变量时,很可能都是从缓存(寄存器)中读取的。某个线程对flag的修改,无法通知到另外一个线程。为此需要使用volatile,保证每次读写都需要有内存访问。这体现了volatile的易变性和不可优化性。与此同时,也引出了一个疑问:volatile的这些特性确信是解决该问题的正确方案么,或者说就没有其他可选的解决方案了么?

显然volatile不能解决这个问题,因为还存在编译器优化和CPU执行指令时的乱序情况(Out-of-Order Execution, OOE。不过上面这个例子在x86-based机器上不会发生OOE的情况,可以看这里了解x86-based CPU乱序的总结)。

所以说,多线程下访问共享数据至少要考虑两点(应该还有其他要考虑的,但是写到这里,我只能列这些):

  1. 数据一致性。保证每次读到的都是最新的数据,每次写都是基于最新的数据。
  2. 指令执行在某种程度上的顺序性。

而volatile关键字根本不能保证这两点内容。所以volatile在多线程下根本没有用。因为volatile类型的数据并不保证数据读写的原子性。并且volatile关键字生成的代码一般情况下不会附带上特殊的CPU指令。因此volatile至少不能控制CPU的乱序执行。

我们再从一个简单的场景来考虑这个问题。假设有多个线程会去读写同一个变量a。我们通常的做法是怎么样的?对,使用锁。为什么?这么高深的问题,我只能借用别人的研究成果了:

Like volatile, the kernel primitives which make concurrent access to data
safe (spinlocks, mutexes, memory barriers, etc.) are designed to prevent
unwanted optimization
.

结论来了,如果有锁在,和锁相关的代码是会被特殊考虑的,不该有的优化是会被屏蔽的(应该是编译器的代码生成和CPU执行指令两方面都有影响)。所以你希望volatile能做的事情(虽然它做不到),锁或者内存屏障都能做。并且在使用这些工具的时候,根本不需要volatile的参与。

至此,关于不需要使用volatile的论证基本就结束了。

回到何的文章,后面还介绍了为什么会有volatile这个关键字,这个关键字解决了什么问题。这里想补充说的是,volatile关键字并不是定义了一个和数据内容相关的属性;volatile关键字是定义了一个和数据访问相关的属性。从当初volatile被设计为用于MMIO(Memory Mapped IO)以及C/C++最初并不包含多线程的概念可以看出volatile并不是为了多线程而设计的。因此将volatile应用于多线程本身就不合适。

In C, it's "data" that is volatile, but that is insane. Data
isn't volatile - _accesses_ are volatile
. So it may make sense to say
"make this particular _access_ be careful", but not "make all accesses to
this data use some random strategy".

UPDATE: 2014-1-22

这里再补充点Visual C++关于volatile关键字的特别之处。

Visual C++ 2005之后,volatile关键字和其他高级语言,比方说C#会比较接近。直接来看MSDN的描述:

Objects declared as volatile are not used in certain optimizations because their values can change at any time. The system always reads the current value of a volatile object at the point it is requested, even if a previous instruction asked for a value from the same object. Also, the value of the object is written immediately on assignment.

Also, when optimizing, the compiler must maintain ordering among references to volatile objects as well as references to other global objects. In particular,

  • A write to a volatile object (volatile write) has Release semantics; a reference to a global or static object that occurs before a write to a volatile object in the instruction sequence will occur before that volatile write in the compiled binary.
  • A read of a volatile object (volatile read) has Acquire semantics; a reference to a global or static object that occurs after a read of volatile memory in the instruction sequence will occur after that volatile read in the compiled binary.

This allows volatile objects to be used for memory locks and releases in multithreaded applications.

所以Visual C++ 2005后,volatile对象可以用作memory barrier。

当然,C++11标准后,情况又有了新的变化。在VC没有支持C++11标准前(VC2010及以前) ,对volatile关键字的描述中明确指明了volatile关键字是可以用于解决多线程数据访问的问题的:

The volatile keyword is a type qualifier used to declare that an object can be modified in the program by something such as the operating system, the hardware, or a concurrently executing thread.

但是C++11标准明确了volatile的定义,让他回归了当初设计的本源:

A type qualifier that you can use to declare that an object can be modified in the program by the hardware.

the C++11 ISO Standard volatile keyword is different and is supported in Visual Studio when the /volatile:iso compiler option is specified. (For ARM, it's specified by default). The volatile keyword in C++11 ISO Standard code is to be used only for hardware access; do not use it for inter-thread communication. For inter-thread communication, use mechanisms such as std::atomic<T> from the C++ Standard Template Library.

UPDATE: 2014-2-11

关于memory reordering,Jeff Preshing的这篇文章值得深入阅读。特别是comments部分里罗列的一些资源。这些额外的链接讨论了一个非常有趣的问题,并且不同的人有不同的看法。有人认为该用volatile解决reordering问题。

这个问题是这样的。有如下一段代码:

extern int v;
void f(int set_v)
{
    if (set_v) v = 1;
}

GCC 3.3.4 - 4.3.0带有O1优化的情况下,汇编码是:

f:
    pushl   %ebp
    movl    %esp, %ebp
    cmpl    $0, 8(%ebp)
    movl    $1, %eax
    cmove   v, %eax        ; load (maybe)
    movl    %eax, v        ; store (always)
    popl    %ebp
    ret

从汇编看,即使调用f(0),也会存在一次写v的动作。在多线程环境下,即便f(0)也要加锁(v在多线程下是共享数据)。

这个问题的讨论中,有人认为这是编译器bug;有人认为v应该加上volatile修饰,这样就不会生成这样的汇编码了。

结论是,这个是编译器的bug。

完!

Reference:

  1. Why the “volatile” type class should not be used
  2. doc: volatile considered evil
  3. Lockless Programming Considerations for Xbox 360 and Microsoft Windows
  4. volatile (C++) 2013
  5. volatile (C++) 2005
  6. Optimization of conditional access to globals: thread-unsafe?
  7. Single Threaded Memory Model
  8. -fno-tree-cselim not working?

也来说说C/C++里的volatile关键字的更多相关文章

  1. Java并发编程里的volatile。Java内存模型核CPU内存架构的对应关系

    CPU内存架构:https://www.jianshu.com/p/3d1eb589b48e Java内存模型:https://www.jianshu.com/p/27a9003c33f4 多线程下的 ...

  2. Java 里volatile关键字是什么意思啊?如何使用呢?

    一旦一个并发共享变量(类的成员变量.静态成员变量)被 volatile 关键字修饰就具备了可见性(即一个线程修改了一个变量的值对于另一个线程来说是立即可见的)和有序性(即禁止进行指令重排序),实质是在 ...

  3. volatile关键字 学习记录1

    虽然已经工作了半年了...虽然一直是在做web开发....但是平时一直很少使用多线程..... 然后最近一直在看相关知识..所以就有了这篇文章 用例子来说明问题吧 public class Volat ...

  4. volatile关键字和mutable关键字

    如果不用volatile关键字会如何?可能会造成一个后果就是:编译器发现你多次使用同一个变量的值,然后它可能会假设这个变量是不变的值,并且把这个变量的值放入寄存器中,方便下一次使用,提高存取速度. 一 ...

  5. zz剖析为什么在多核多线程程序中要慎用volatile关键字?

    [摘要]编译器保证volatile自己的读写有序,但由于optimization和多线程可以和非volatile读写interleave,也就是不原子,也就是没有用.C++11 supposed会支持 ...

  6. volatile关键字与线程间通信

    >>Java内存模型 现在计算机普遍使用多处理器进行运算,并且为了解决计算机存储设备和处理器的运算速度之间巨大的差距,引入了高速缓存作为缓冲,缓存虽然能极大的提高性能,但是随之带来的缓存一 ...

  7. volatile 关键字

    就象大家更熟悉的const一样,volatile是一个类型修饰符(type specifier).它是被设计用来修饰被不同线程访问和修改的变量.如果没有volatile,基本上会导致这样的结果:要么无 ...

  8. volatile关键字的使用

    (简要概括:volatile变量有两个作用:一个是告诉编译器不要进行优化:另一个是告诉系统始终从内存中取变量的地址,而不是从缓存中取变量的值) 一.前言 1.编译器优化介绍: 由于内存访问速度远不及C ...

  9. C语言中volatile关键字的作用

    http://blog.csdn.net/tigerjibo/article/details/7427366#comments 一.前言 1.编译器优化介绍: 由 于内存访问速度远不及CPU处理速度, ...

随机推荐

  1. YYYY-mm-dd HH:MM:SS

    备忘:YYYY-mm-dd HH:MM:SS部分解释 d               月中的某一天.一位数的日期没有前导零.    dd             月中的某一天.一位数的日期有一个前导零 ...

  2. AutoCAD Civil 3D 中缓和曲线的定义

    本文对AutoCAD Civil 3D中缓和曲线的定义进行了整理. 原英文网页如下: https://knowledge.autodesk.com/support/autocad-civil-3d/l ...

  3. css3动画特效:上下晃动的div

    css3动画特效:上下晃动的div <div id="square" class="container animated">上下晃动</div ...

  4. request和session作用域的意义

    1.简单说 page指当前页面.在一个jsp页面里有效 ,page里的变量没法从index.jsp传递到test.jsp.只要页面跳转了,它们就不见了.2.request 指从http请求到服务器处理 ...

  5. 通过页面调用APP【H5与APP互通】

    现在H5和App原生的内容原来越互通,所涉及的业务也越来越复杂和融合,所以如何互相之间方便的调用才是王道. 场景1 比如用hybrid获取地理位置和短信信息,这当然需要框架封装好,比如利用框架的bri ...

  6. hdu1754 I Hate It

    题目链接:hdu1754 I Hate It 树状数组学习参考博客:http://blog.csdn.net/u010598215/article/details/48206959 树状数组之前没看懂 ...

  7. SAP播放本地视频及音频(仅限于window MediaPlayer可播放文件)

    这个是从SCN上看到的,自己稍加修改,编制,做的还可以,可以播放视频,音频,唯一的不足就是不能控制播放视频的显示窗口大小,希望有人能帮忙解决,感激! 视频播放类:(新建类Z_CL_MEDIA,点击基于 ...

  8. MVC中获取来自控制器名称与动作的方法

    #region 获取控制器名称与动作 protected void GetNameSpace() { var nameSpace = this.RouteData.Values["contr ...

  9. java selenium (二) 环境搭建方法一

    webdriver 就是selenium 2.    webdriver 是一款优秀的,开源的,自动化测试框架. 支持很多语言.  本文描述的是用java Eclipse 如何搭建环境 阅读目录   ...

  10. sql数据库表被锁,无法查询

    查看被锁表:   select   request_session_id   spid,OBJECT_NAME(resource_associated_entity_id) tableName    ...