易挥发。保证新鲜度

时间:2019-03-19 04:17:28

标签: c# multithreading memory-barriers

Volatile.Write的文档中指出:

  

将指定的对象引用写入指定的字段。上   需要它的系统,插入一个内存屏障,以防止   处理器对存储器操作进行重新排序,如下所示:如果读取或   在代码中此方法之前出现写操作,处理器无法   用这种方法移动它。

  

T
  要写入的对象引用。参考是书面的   立即使它对计算机中的所有处理器可见。

但是似乎引号1和2是矛盾的。

要使第二个引号正确,我认为必须将第一个引号更改如下:

  

如果阅读或   在代码中此方法在之前 之后出现,处理器无法   在此方法之前之后 之前

Volatile.Write是否实际上意味着可以保证其他线程能够及时进行写操作,还是第二个引号引起误解?

在我看来,似乎所有这些“易失性” /“内存壁垒”似乎都集中在确保如果将写入公开给其他线程,则它们以正确的顺序公开,但是我似乎找不到实际上是什么迫使他们暴露在外。

我知道可能很难/不可能立即将写入公开给其他线程,但是在没有易失性写入/读取的情况下,有些情况下从不公开。因此,似乎必须有一种方法可以确保“最终”公开写入,但是我不确定那是什么。是写总是在.NET中公开,但读可以缓存吗?如果这样,Volatile.Read会停止这种缓存行为吗?

(请注意,我已经通读了Joseph Albahari's Threading in C#,这往往表明我在读和写后需要显式的内存屏障,尽管目前尚不清楚为什么Thread.MemoryBarrier作为https://developers.google.com/maps/documentation/android-sdk/polygon-tutorial的文档也是有效的似乎没有明确地说写的内容已显示给其他线程。

2 个答案:

答案 0 :(得分:3)

您有点误解了障碍的概念。如你所写

  

要写入的对象引用。该引用会立即写入,以便计算机中所有处理器都可以看到。

因此,这里真正重要的单元是处理器,而不是线程。

因此,涉及到处理器,处理器缓存,存储缓冲区和失效队列。
当处理器将某些内容写入内存时,看起来像是类似的东西 enter image description here

主题位于商店缓冲区级别。如您所见,当您写东西或读东西时,有很多事情在发生,并且并不是系统中所有处理器都会立即发生。最初,将读取或写入命令放入处理器的存储缓冲区中,并且这些命令可以重新排序,换句话说,由处理器以不同的顺序执行。

在这种情况下,其他处理器不知道更改,如果操作是写操作,并且当前工作的处理器不知道其他处理器所做的更改。

放置屏障时,这意味着在执行任何读取或写入操作之前,应先完成存储缓冲区或失效队列中的操作。 这对于在处理器之间实现CPU缓存是必需的。因此,基本上没有机制可以跨线程同步任何数据,而我们可以跨处理器同步数据。

当线程A在处理器1上写东西而线程B在处理器1上读东西时,它们都首先从存储缓冲区中查找,因此它们读取实际数据,无论是否设置了障碍。

这只是所涉及机制的概述,也许我在某些细节上是错误的。如果您了解有关MESI protocolthis PDF with explanation on invalidation queues and store buffers

的信息,则可以找到完整的信息。

答案 1 :(得分:2)

我同意您的观点,即MSDN文档中的描述有些混乱。我想说的是,“立即”在这里以及与并行处理相关的任何主题上都是强词。结果将不会立即可见,但文档并未说明-它表示将立即写入值,也就是说,一旦所有先前的加载/存储操作结果在全局范围内可见,则写入值的存储操作将被立即写入。立即启动。

对于内存屏障,它们只能保证先前的操作暴露(全局可见性),因为实质上,内存屏障是CPU遇到的指令,使CPU“等待”以获取所有挂起的加载/存储Volatile.Write所写的价值的全球可见性时刻既不是障碍也不是Volatile.Write的关注。

现在有关在无锁编程中使用障碍的建议。当然这是有道理的,因为它确保了多核系统实际的全局可见性顺序。如果您不能确定事件B总是在事件A之后发生,那么您就无法构建可靠的逻辑,该逻辑应该在多核环境中执行。