19

根据这本在线书籍volatileC# 中的关键字不能防止重新排序写操作后跟读操作。它给出了这个例子,其中ab最终都可以设置为0,尽管xyvolatile

class IfYouThinkYouUnderstandVolatile
{
  volatile int x, y;
 
  void Test1()        // Executed on one thread
  {
    x = 1;            // Volatile write (release-fence)
    int a = y;        // Volatile read (acquire-fence)
    ...
  }
 
  void Test2()        // Executed on another thread
  {
    y = 1;            // Volatile write (release-fence)
    int b = x;        // Volatile read (acquire-fence)
    ...
  }
}

这似乎符合 10.5.3 中规范所说的内容:

对易失性字段的读取称为易失性读取。易失性读取具有“获取语义”;也就是说,它保证在指令序列中对内存的任何引用之前发生。

对易失性字段的写入称为易失性写入。易失性写入具有“释放语义”;也就是说,它保证发生在指令序列中写指令之前的任何内存引用之后。

这是什么原因?是否存在我们不介意对写入-读取操作进行重新排序的用例?

4

3 回答 3

7

Volatile 不保证独立 volatile 变量的读取和写入不会重新排序,它只保证读取得到最新的值(非缓存)。(保证对单个变量的读写保持顺序)

http://msdn.microsoft.com/en-us/library/x13ttww7%28v=vs.71%29.aspx

系统总是在请求时读取 volatile 对象的当前值,即使之前的指令要求来自同一对象的值。此外,对象的值在赋值时立即写入。

volatile修饰符通常用于被多个线程访问而不使用lock语句序列化访问的字段。使用 volatile 修饰符可确保一个线程检索另一个线程写入的最新值。

每当您有多个依赖操作时,您都需要使用其他一些同步机制。通常使用lock,它是最简单的,并且只会在被滥用或在非常极端的情况下造成性能瓶颈。

于 2012-07-31T20:30:49.833 回答
4

volatile write在 x86/x64 架构上通过重新排序进行预防volatile read将非常昂贵。那是因为称为store buffers. Java 采用了这种方式,Java 中的 volatile 写入实际上是 CPU 指令级别的完整内存屏障。

于 2016-07-15T16:19:22.563 回答
3

也许回答太晚了……但我环顾四周,看到了这个。Volatile-read 实际上是对类似于以下的方法的调用:

public static int VolatileRead(ref int address)
{
    int num = address;
    Thread.MemoryBarrier();
    return num;
}

而 Volatile-write 是这样的:

public static int VolatileWrite(ref int address, int value)
{
    Thread.MemoryBarrier();
    adrdress = value;
}

该指令MemoryBarrier();是防止重新排序的指令。MemoryBarrier();确保之前的指令在之后的指令之前执行。当 VW 然后 VR,你将拥有:

Thread.MemoryBarrier();
adrdress = value; //this line may be reordered with the one bellow
int num = address;//this line may be reordered with the one above
Thread.MemoryBarrier();
return num;
于 2015-11-16T14:46:59.000 回答