13

像许多其他人一样,我一直对易失性读/写和栅栏感到困惑。所以现在我试图完全理解这些是做什么的。

因此,volatile 读取应该 (1) 表现出获取语义和 (2) 保证读取的值是新鲜的,即,它不是缓存值。让我们关注(2)。

现在,我已经读到,如果您想执行易失性读取,您应该在读取之后引入一个获取栅栏(或完整栅栏),如下所示:

int local = shared;
Thread.MemoryBarrier();

这究竟如何防止读取操作使用先前缓存的值?根据栅栏的定义(不允许在栅栏上方/下方移动读取/存储),我会在读取之前插入栅栏,防止读取穿过栅栏并及时向后移动(又名,被缓存)。

防止读取及时向前移动(或后续指令及时向后移动)如何保证易失性(新)读取?它有什么帮助?


同样,我认为 volatile 写入应该在写入操作之后引入栅栏,防止处理器及时向前移动写入(也就是延迟写入)。我相信这会使处理器刷新对主存的写入。

但令我惊讶的是,C# 实现在写入之前引入了栅栏!

[MethodImplAttribute(MethodImplOptions.NoInlining)] // disable optimizations
public static void VolatileWrite(ref int address, int value)
{
    MemoryBarrier(); // Call MemoryBarrier to ensure the proper semantic in a portable way.
    address = value;
}

更新

根据这个例子,显然取自“C# 4 in a Nutshell”,放置写入之后的栅栏 2 应该强制写入立即刷新到主内存,放置在读取之前的栅栏 3 应该保证新鲜读物:

class Foo{
  int _answer;
  bool complete;
  void A(){
    _answer = 123;
    Thread.MemoryBarrier(); // Barrier 1
    _complete = true;
    Thread.MemoryBarrier(); // Barrier 2
  }
  void B(){
    Thread.MemoryBarrier(); // Barrier 3;
    if(_complete){
      Thread.MemoryBarrier(); // Barrier 4;
      Console.WriteLine(_answer);
    }
  }
}

本书中的想法(以及我个人的信念)似乎与 C#VolatileReadVolatileWrite实现背后的想法相矛盾。

4

2 回答 2

9

这究竟如何防止读取操作使用先前缓存的值?

它没有这样的事情。易失性读取不保证将返回最新值。用简单的英语来说,它真正的意思是下一次读取将返回一个更新的值,仅此而已。

防止读取及时向前移动(或后续指令及时向后移动)如何保证易失性(新)读取?它有什么帮助?

请注意此处的术语。挥发性不是新鲜的代名词。正如我上面已经提到的,它的真正用处在于如何将两个或多个易失性读取链接在一起。易失性读取序列中的下一次读取绝对会返回比上一次读取相同地址的新值。编写无锁代码时应牢记这一前提。也就是说,代码的结构应该以处理更新值而不是最新值为原则。这就是为什么大多数无锁代码在一个循环中旋转,直到它可以验证操作完全成功。

本书中的想法(以及我个人的信念)似乎与 C# 的 VolatileRead 和 VolatileWrite 实现背后的想法相矛盾。

并不真地。记住易挥发的!=新鲜的。是的,如果你想要一个“新鲜”的阅读,那么你需要在阅读之前放置一个获取栅栏。但是,这与进行易失性读取不同。我要说的是,如果在read 指令之前VolatileRead调用了 to ,那么它实际上不会产生volatile读取。如果会产生新鲜的阅读。Thread.MemoryBarrier

于 2014-08-04T01:24:01.090 回答
6

要理解的重要一点是,volatile这不仅意味着“不能缓存值”,而且还提供了重要的可见性保证(确切地说,完全有可能进行仅用于缓存的易失性写入;仅取决于硬件及其使用缓存一致性协议)

易失性读取提供获取语义,而易失性写入具有释放语义。获取栅栏意味着您不能在栅栏之前重新排序读取或写入,而释放栅栏意味着您不能在栅栏之后移动它们。评论中的链接答案实际上很好地解释了这一点。

现在的问题是,如果我们在加载之前没有任何内存屏障,那么如何保证我们会看到最新的值?答案是:因为我们还在每次 volatile 写入之后设置了内存屏障来保证这一点。

Doug Lea 写了一篇关于存在哪些障碍、它们做什么以及将它们放置在何处以用于 JMM 的易失性读/写的精彩总结,作为对编译器编写者的帮助,但该文本对其他人也非常有用。易失性读取和写入在 Java 和 CLR 中提供相同的保证,因此这通常适用。

来源- 向下滚动到“内存屏障”部分(我会复制有趣的部分,但格式无法保留..)

于 2014-02-09T00:15:59.150 回答