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

时间:2009-06-30 11:18:03

标签: java c++ volatile

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

  1. 允许访问内存映射设备
  2. 允许在setjmp和longjmp之间使用变量
  3. 允许在信号处理程序中使用变量
  4. 忙着等待
  5. 我可以开始看到这个说明符在上面列出的用法中的作用,但由于我还没有完全理解这些方面的每一个,我无法弄清楚这个说明符在这些方面的行为究竟如何用途

    有人可以解释一下吗?

8 个答案:

答案 0 :(得分:12)

您的问题在技术上被称为“一罐蠕虫”! 对于c / c ++(我不能评论java)
你可以非常粗略地总结volatile作为编译器的指令,说“请不要优化它”,但专业人士之间有很多争论,至于是否是 a)At all useful for kernel level code< -Edit根据反馈澄清 b)Even implemented correctly by most compilers.

此外,不要将它用于多线程编程和here's a very good explanation as to why

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

答案 1 :(得分:6)

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

内存映射设备是处理器以与内存相同的方式与之通信的外设,而不是通过专用总线。

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

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

现在想想另一种检查灯光和状态的情况。它开着。这里,该值是从设备的存储器中提取的。保存在处理器寄存器中。现在,几秒钟后,灯自动关闭。此后不久,您尝试再次打开灯,但是因为您读取了该存储器地址&由于没有改变它,编译器认为该值仍然是一个&因此,永远不会改变它,虽然现在实际上是0。

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

答案 2 :(得分:4)

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

Dr.Dobbs volatile vs. volatile

答案 3 :(得分:2)

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

答案 4 :(得分:2)

易失性变量在Java中很有用(至少从Java 5.0开始,他们的behaviour changed)正如Brian Goetz在他的书“Java Concurrency in Practice”(JCIP)中所说的那样 - 关于这个主题的必备书(第37页) ):

  

确保更新变量   可预测地传播给其他人   线程

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

// 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支持原子变量。这些允许更改值在线程/处理器之间以与volatile变量类似的方式显示,但也允许执行“Compare And Set”操作,这意味着可以安全地执行某些其他类型的并发操作而不进行锁定。

答案 5 :(得分:1)

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

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

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

答案 6 :(得分:0)

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

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

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

答案 7 :(得分:-1)

当许多线程可以访问该变量并且您希望每条指令都能获得该变量的更新值时,应该使用易失性变量。

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

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