在哪里放置围栏/内存屏障以保证新的读取/提交写入?

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

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

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

int local = shared; Thread.MemoryBarrier(); 

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

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


类似地,我认为易失性写入应该在写入操作之后引入栅栏,从而阻止处理器及时向前移动写入(也就是说,延迟写入)。 我相信这会使处理器刷新对主内存的写入。

但令我惊讶的是, 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”中取出的, 写入之后放置的栅栏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实现背后的想法相矛盾。

这究竟是如何阻止读取操作使用以前缓存的值?

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

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

这里的术语要小心。 挥发性不是新鲜的同义词。 正如我上面已经提到的,它的真正用处在于如何将两个或多个易失性读取链接在一起。 一系列易失性读取中的下一次读取将绝对返回比先前读取的相同地址更新的值。 应该在考虑此前提的情况下编写无锁代码。 也就是说,代码应该被构造为处理更新值而不是最新值的原则。 这就是为什么大多数无锁代码在循环中旋转,直到它可以validation操作完全成功为止。

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

并不是的。 记得不稳定!=新鲜。 是的,如果你想要一个“新鲜”的阅读,那么你需要在阅读之前放置一个获取围栏。 但是,这与进行易失性读取不同。 我所说的是,如果VolatileRead的实现在读取指令之前调用了Thread.MemoryBarrier 那么它实际上不会产生易失性读取。 如果会产生的读数。

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

易失性读取提供获取语义,而易失性写入具有释放语义。 获取围栏意味着您不能在围栏之前重新排序读取或写入,而释放围栏意味着您无法在围栏之后移动它们。 评论中的链接答案解释说实际上相当不错。

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

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

来源 – 向下滚动到“记忆障碍”部分(我会复制有趣的部分,但格式不存在……)