在C / C ++ / Java中使用volatile说明符

在multithreading编程上经历许多资源时,通常会出现对volatile说明符的引用。 很明显,使用此关键字并不是在C / C ++和Java(版本1.4及更早版本)中实现多个线程之间同步的可靠方法。 以下是维基百科列出(不解释如何)作为此说明符的典型用法: –

  1. 允许访问内存映射设备
  2. 允许在setjmp和longjmp之间使用变量
  3. 允许在信号处理程序中使用变量
  4. 忙着等待

我可以开始看到这个说明符在上面列出的用法中的作用,但由于我还没有完全理解这些领域中的每一个,我无法弄清楚这个说明符在每个用法中的行为。

有人能解释一下吗

由于您对这些用例感兴趣,我将解释第一个用例。 请注意,这适用于ac / c ++的角度,不知道它如何在java中起作用,尽管我怀疑c / c ++中的volatile通常用于完全不同的情况。

存储器映射设备是处理器以与存储器相同的方式而不是通过特殊总线与之通信的外围设备。

假设您有一个带有内存映射计时器的灯。 您可以通过将1写入其内存地址来打开指示灯并且其内部计时器倒计时5秒钟并关闭指示灯并将内存位置重置为0.现在您正在开发需要在某些事件后打开指示灯的交流程序,有时在柜台到期前将其关闭。 如果使用常规变量(往往是此类应用程序的指针或引用)来写入其内存位置,则由于编译器优化,可能会出现许多问题。

如果你没有处理那么多变量而且你正在打开它并且在关闭它之后不使用该值而没有任何其他变量 – 有时编译器将完全摆脱第一个赋值,或者在其他情况下它会只需保持处理器寄存器中的值,永远不要写入内存。 在这两种情况下,光线都不会打开,因为它的记忆从未改变过。

现在想想另一种情况,你可以检查灯的状态并打开它。 这里,该值从器件的存储器中提取并保存在处理器寄存器中。 现在,几秒钟后,灯自动关闭。 此后不久,您尝试再次打开灯,但是因为您读取了内存地址并且之后没有更改它,编译器会假定该值仍为1,因此永远不会更改它,尽管现在实际上是0。

通过使用易失性关键字,可以防止编译器在将代码转换为机器代码时进行任何这些假设,并确保所有这些特定操作都严格按程序员编写的方式执行。 这对于内存映射设备至关重要,主要是因为处理器不会严格更改内存位置。 出于同样的原因,具有共享内存的多处理器系统在公共内存空间上运行时通常需要类似的实践。

您的问题在技术上被称为“一jar蠕虫”! 对于c / c ++(我不能评论java)
你可以非常粗略地总结volatile作为编译器的指令,说’请不要优化它’但专业人士之间有很多争论,至于它是否是
a) 对内核级代码非常有用 <-Edit基于反馈澄清
b) 大多数编译器都能正确实现。

此外,不要将它用于multithreading编程, 这里有一个很好的解释为什么

=编辑=有趣的是,它的价值。 Dennis Ritchie在这里反对它的包含(以及const)细节

我发现Herb Sutter的这篇DDJ文章非常有趣,特别是在C ++,Java和C#.NET中如何处理volatile。

Dr.Dobbs易挥发与易挥发

这里有一个很好的解释: http : //en.wikipedia.org/wiki/Volatile_variable但略微简化它告诉编译器它不应该假设该变量不被其他人访问,并且优化它是致命的进入注册商并仅更新注册而不是实际存储。

易失性变量在Java中很有用(至少从Java 5.0开始,其行为发生了变化 ),正如Brian Goetz在他的书“实践中的Java并发”(JCIP)中所说的那样 – 关于这个主题的必备书(第37页):

确保对变量的更新可预测地传播到其他线程

显式同步也可以实现这一点,但通常我们并不总是想要锁定一个值。 Double Checked锁定是这个的典型例子(从维基百科复制):

// Works with acquire/release semantics for volatile // Broken under Java 1.4 and earlier semantics for volatile class Foo { private volatile Helper helper = null; public Helper getHelper() { if (helper == null) { synchronized(this) { if (null == helper) helper = new Helper(); } } return helper; } // other functions and members... } 

如果助手不易变,这将无效。

易失性变量也可用于实现非锁定并发数据结构,例如java.util.concurrent.ConcurrentHashMap(它支持并发更新和访问而无需锁定 – 请参阅JDK源代码以了解其使用的volatile)。

JCIP对双重检查锁定,volatile变量和Java并发性进行了很好的讨论。 Joshua Bloch撰写的“Effective Java”,第2版也值得一读。

另请注意, java.util.concurrent.atomic包中的Java支持primefaces变量。 这些允许更改值在线程/处理器之间以与volatile变量类似的方式显示,但也允许执行“比较和设置”操作,这意味着可以安全地执行一些其他类型的并发操作而不锁定。

volatile关键字很久以前就出现在C语言中了,它的作用基本上是“关闭”一些编译器优化,假设变量没有明确更改,它根本没有改变。 它的主要用途是声明可由中断处理程序更改的变量。 例如,我使用它一次(80年代后期)用于包含鼠标光标位置的全局变量。 中断改变了位置,没有volatile,主程序有时不会检测到它的变化,因为编译器优化了变量访问,认为没有必要。

今天这些用途一般都是过时的(除非你编写低级操作系统代码),但仍然有一些罕见的情况,其中volatile是有用的(非常罕见 – 例如,我可能没有使用它的最后一个7年)。

但对于multithreading编程,它完全不受推荐。 问题是它不能保护线程之间的并发访问,它只会删除会阻止在同一线程中“刷新”的优化。 它不适用于multithreading环境。 如果您使用的是Java,请使用synchronized。 如果您使用的是C ++,请使用一些同步库,例如pthreads或Boost.Threads(或者,更好的是,如果可以的话,使用新的C ++ 0X线程库)。

我做C ++已经有一段时间了,我真的不记得那种语言中的volatine的定义。 但Java语言规范明确指出volatile的目的是促进对变量的multithreading访问。 引用:“字段可能被声明为volatile,在这种情况下,Java内存模型(第17节)确保所有线程都看到变量的一致值。” 他们接着说保证对volatile表达式的引用按照它们在代码中指定的顺序得到满足,即如果你声明i和j volatile然后写“++ i; ++ j”,那么我事实上,总是会在j之前增加。

我记得在Java中使用volatile的唯一一次是当我有一个线程可能设置一个取消标志而另一个线程循环通过一些大操作并且每次通过循环检查取消标志。 这确实按照我的预期工作。

我同意“易变”的用处非常有限。 大多数multithreading需要在某些时候“同步”。 但“有限”和“无”不是一回事。 余弦函数在大多数业务应用程序中的用途非常有限。 但是当你需要它时,哇,这可以省去很多麻烦。

当许multithreading可以访问该变量并且您希望在每条指令中您的代码都应获取该变量的更新值时,应该使用易失性变量。

编译器通常优化代码并将变量存储在寄存器中,而不是每次都看到没有人更新它时从内存中获取。

但是通过使用volatile,您可以强制编译器每次都获取更新的值。