为什么C#volatile没有保护写 - 读重新排序?

时间:2012-07-31 18:56:01

标签: c# .net multithreading volatile

根据this online book,C#中的volatile关键字不能防止重新排序Write操作,然后执行Read操作。它提供了一个示例,其中ab最终都设置为0,尽管xyvolatile:< / p>

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中的规范:

  

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

     

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

这是什么原因?是否有一个用例,我们不介意重写读写操作?

3 个答案:

答案 0 :(得分:7)

Volatile不保证读取和写入不会重新排序,它只保证读取获得最新的值(非缓存)。

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

  

系统始终在请求点读取易失性对象的当前值,即使前一条指令要求来自同一对象的值也是如此。此外,对象的值在分配时立即写入。

     

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

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

答案 1 :(得分:2)

回答可能为时已晚......但我环顾四周,看到了这一点。 易失性读取实际上是对类似于以下方法的调用:

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();确保之前的intructions在之后的intructions之前执行。 当大众然后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;

答案 2 :(得分:2)

在x86 / x64 arch上预防255 volatile write重新排序将非常昂贵。这是因为称为volatile read的写入优化。 Java采用这种方式,Java中的易失性写入实际上是CPU指令级别的完全内存屏障。