去年年底的样子,何登成写了一篇关于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. linux node安装

    安装node0.10.24版本,升级了两个版本/usr/local/src/node/0.10.24/usr/local/n/versions/node/4.4.7/usr/local/n/versi ...

  2. 《CSS权威指南》读书笔记

    一.css和文档层叠 css规定了冲突规则,这些规则统称为层叠.这些规则定义了样式发生冲突时以优先级高的为准. 常用的优先级判定: 1. 开发者样式>读者样式>浏览器样式(除非使用!imp ...

  3. 基于注解的Spring AOP的配置和使用

    摘要: 基于注解的Spring AOP的配置和使用 AOP是OOP的延续,是Aspect Oriented Programming的缩写,意思是面向切面编程.可以通过预编译方式和运行期动态代理实现在不 ...

  4. 萝卜招聘网 http://www.it9s.com 可以发布免费下载简历求职 ,免费!免费!全部免费!找工作看过来 免费下载简历 !

    萝卜招聘网  http://www.it9s.com  可以发布免费下载简历求职 ,免费!免费!全部免费!找工作看过来 免费下载简历 !萝卜招聘网  http://www.it9s.com  可以发布 ...

  5. Java之工厂方法

    普通工厂模式: 第一步:定义接口,坚持面向接口编程, package dp; public interface Sender {    public void send();} 第二步:实现接口: p ...

  6. Js数组排序函数sort()

    JS实现多维数组和对象数组排序,用的其实就是原生sort()函数,语法为:arrayObject.sort(sortby)(sortby 可选.规定排序顺序.必须是函数.) 返回值为对数组的引用:请注 ...

  7. (Jquery)关于给动态加载的页面元素,绑定事件

    如果使用Jquery给元素绑定事件,一般会用bind,或者类似click函数来直接绑定. 但是对于动态生成的元素,会发现常规绑定无法生效,比如: <div class'div'></ ...

  8. 我需要在Web上完成一个图片上传的功能后续(+1)

    微信入口施工完成.关键字识别中增加了本次活动的"关键字",在系统中增加了链接.不过,由于地址包含私密关键参数,这里隐藏,敬请原谅. 下一步,微信链接的地址页面是要对微信用户的信息进 ...

  9. 【Yeoman】热部署web前端开发环境

    本文来自 “简时空”:<[Yeoman]热部署web前端开发环境>(自动同步导入到博客园) 1.序言 记得去年的暑假看RequireJS的时候,曾少不更事般地惊为前端利器,写了<Sp ...

  10. hihocoder挑战赛26

    某蒟蒻成功的·写出了T1并rank16...小岛的题目真难... 传送门:http://hihocoder.com/contest/challenge26 T1 如果你想要暴力枚举的话显然是不行的 如 ...