为什么 C# 易失性不保护写-读重新排序?



根据这本在线书籍,C#中的volatile关键字不能防止写入操作后的读取操作的重新排序。它给出了一个例子,其中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:中的规范要求

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

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

这是什么原因?有没有一个用例我们不介意重新排序写-读操作?

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

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

系统总是在请求易失性对象时读取该对象的当前值,即使上一条指令要求从同一对象获取值也是如此。此外,对象的值会在赋值时立即写入。

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

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

在x86/x64 arch上,用volatile read重新排序来阻止volatile write将非常昂贵。这是因为称为store buffers的写优化。Java就是这样做的,Java中的易失性写入实际上是CPU指令级别的满内存屏障。

也许太晚了,无法回答。。。但我环顾四周,看到了这个。易失性读取实际上是对类似于以下方法的调用:

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

易失性写入是这样的:

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

指令CCD_ 12是防止重新排序的指令。MemoryBarrier();确保之前的指令在之后的指令之前执行。当大众然后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;

最新更新