去年年底的样子,何登成写了一篇关于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. 基础笔记10(IO 1.7try-with-resource) 装饰模式

    1.读写的类型分为字节流和字符流,字节流一般是视频音频其他所有的类型都可以. (非文档文件使用字符流易造成未知编码(?)错误) InputStream OutputStream 抽象类 fileInp ...

  2. js判断当前设备

    最近用bootstrap做自适应,发现仍然很难很好的兼容web端和PC端的现实. 仔细观察百度,淘宝,京东等大型网站,发现这些网站都有对应不同客户端的子站. 例如: 站点 PC端url web端url ...

  3. Linux查找

    如果你想在当前目录下 查找"hello,world!"字符串,可以这样: grep -rn "hello,world!" * * : 表示当前目录所有文件,也可 ...

  4. IaaS、PaaS、SaaS 之间的区别

    IaaS.PaaS.SaaS 之间的区别 “云服务”现在已经快成了一个家喻户晓的词了.如果你还不知道PaaS.IaaS和SaaS的区别,那就太out了. “云”其实是互联网的一个隐喻,“云计算”其实就 ...

  5. ajax 跨域访问

    后台方法添加 HttpServletResponse response=ServletActionContext.getResponse(); response.addHeader("Acc ...

  6. freemarker数字格式化

    1.在模板中直接加.toString()转化数字为字符串,如:${languageList.id.toString()}: 2.在freemarker配置文件freemarker.properties ...

  7. 使用JS实现轮播图的效果

    其中的一些css样式代码就省略了,下面只把结构层html.行为层js的代码展示出来 ,看代码说事. 一.简单的轮播图 <div class="box" id="bo ...

  8. 【转】 memcmp源码实现

    原型: int memcmp(void *buf1, void *buf2, unsigned int count); 用法:#include <string.h> 功能:比较内存区域bu ...

  9. web storage和cookie的区别

    Web Storage的概念和cookie相似,区别是它是为了更大容量存储设计的.Cookie的大小是受限的,并且每次你请求一个新的页面的时候Cookie都会被发送过去,这样无形中浪费了带宽,另外co ...

  10. PHP Switch case 条件并用实例

    众所周知,Switch循环比if...else...循环效率要好的多,当case有相同代码结构的时候,怎么样来简化代码结构,能让代码更具有通用性呢? 在网上找了一下,好多都是复制粘贴,还有的看起来太复 ...