5

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

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

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

有人可以解释一下吗?

4

8 回答 8

12

您的问题在技术上被称为“一罐蠕虫”!对于 c/c++(我不能对 java 发表评论)
您可以非常粗略地将 volatile 概括为对编译器说“请不要优化它”的指令,但专业人士之间存在很多争论,关于是否这是
a)对内核级代码完全有用 <-根据反馈进行了澄清
b)即使大多数编译器都正确实现了。

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

=Edit= 有趣的是,它的价值。Dennis Ritchie 反对在此处包含(以及 const )详细信息

于 2009-06-30T11:37:39.187 回答
7

Since you're interested in those usage cases, I'll explain the first one. Note that this applies from a c/c++ perspective, not sure how it plays into java, although I suspect in general volatile in c/c++ and java are used for completely different cases.

Memory mapped devices are peripherals which the processor communicates with in the same manner as the memory rather than through a special bus.

Suppose you have a little light with a timer that is memory mapped. You turn on the light by writing 1 to its memory address & its internal timer counts down for 5 seconds & turns the light off and resets the memory location to 0. Now you are developing a c program that needs to turn that light on after certain events, and sometimes turn it off before the counter expires. If you use a regular variable (tends to be a pointer or a reference for this type of application) to write to its memory location, there are a number of things that might go wrong due to compiler optimizations.

If you aren't working with that many variables and you are turning the light on and shortly there after turning it off without any other variables using that value - sometimes the compiler will altogether get rid of the first assignment, or in other cases it will simply maintain the value in the processor registers & never write to memory. In both these cases, the light woudl never turn on since it's memory was never changed.

Now think of another situation where you check the state of the light & it is on. Here, the value is extracted from the device's memory & kept in a processor register. Now, after a few seconds, the light turns off by itself. Shortly thereafter you try to turn the light on again, however since you read that memory address & haven't changed it since, the compiler assumes the value is still one & therefore never changes it, although it is actually 0 now.

By using the volatile key word, you prevent the compiler from making any of those assumptions when converting your code into machine code & ensures all those specific operations are performed strictly as written by the programmer. This is essential for memory mapped devices mostly because the memory location is not changed strictly by the processor. For these same reasons, multiprocessor systems with shared memory often require similar practices when operating on a common memory space.

于 2009-06-30T18:13:56.087 回答
4

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

Dr.Dobbs 易失性与易失性

于 2009-06-30T13:42:03.757 回答
2

正如 Brian Goetz 在他的“Java Concurrency in Practice”(JCIP)一书中所说的,Volatile 变量在 Java 中很有用(至少从 Java 5.0 开始,它们的行为发生了变化)——关于该主题的基本书籍(第 37 页):

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

显式同步也可以实现这一点,但通常我们并不总是想要锁定一个值。双重检查锁定是这方面的经典示例(从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 源代码了解其对易失性的使用)。

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

另请注意,java.util.concurrent.atomic包中的 Java 支持原子变量。这些允许以与 volatile 变量类似的方式跨线程/处理器使对值的更改可见,但也允许执行“比较和设置”操作,这意味着可以安全地执行某些其他类型的并发操作而无需锁定。

于 2009-06-30T21:28:10.480 回答
2

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

于 2009-06-30T11:26:10.630 回答
1

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

今天,这些用途通常已经过时(除非您编写低级 OS 代码),但仍有一些罕见的情况下 volatile 有用(确实非常罕见 - 例如,我可能最后没有使用它7年)。

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

于 2009-06-30T19:35:36.310 回答
0

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

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

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

于 2009-06-30T17:30:14.247 回答
-1

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

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

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

于 2009-06-30T11:31:23.583 回答