C# 在何处设置围栏/内存屏障以保证新的读/写操作?

C# 在何处设置围栏/内存屏障以保证新的读/写操作?,c#,.net,multithreading,volatile,memory-fences,C#,.net,Multithreading,Volatile,Memory Fences,和许多其他人一样,我总是被易变的读/写和围栏弄糊涂。所以现在我正试图完全理解它们的作用 因此,一个易失性读取应该(1)表现出获取语义,(2)保证读取的值是新的,即,它不是缓存的值。让我们关注(2) 现在,如果要执行易失性读取,应该在读取后引入获取围栏(或完整围栏),如下所示: int local = shared; Thread.MemoryBarrier(); 这究竟是如何防止读取操作使用以前缓存的值的? 根据围栏的定义(不允许在围栏上方/下方移动读取/存储),我会在读取之前插入围栏,防止读

和许多其他人一样,我总是被易变的读/写和围栏弄糊涂。所以现在我正试图完全理解它们的作用

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

现在,如果要执行易失性读取,应该在读取后引入获取围栏(或完整围栏),如下所示:

int local = shared;
Thread.MemoryBarrier();
这究竟是如何防止读取操作使用以前缓存的值的? 根据围栏的定义(不允许在围栏上方/下方移动读取/存储),我会在读取之前插入围栏,防止读取越过围栏并在时间上向后移动(也称为缓存)

防止读取在时间上向前移动(或后续指令在时间上向后移动)如何保证易变(新)读取?这有什么帮助


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

但令我惊讶的是,在写作之前,这篇文章介绍了篱笆

[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#的
volatireRead
volatireWrite
实现背后的观点相矛盾。

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

volatile read提供获取语义,volatile write提供释放语义。acquire fence意味着不能在fence之前对读取或写入进行重新排序,而release fence意味着不能在fence之后移动它们。这实际上很好地解释了这一点

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

Doug Lea写了一篇很好的总结,介绍了JMM的易失性读/写存在哪些障碍、它们做了什么以及将它们放在何处,作为编译器编写人员的帮助,但本文对其他人也很有用。易失性读和写在Java和CLR中都提供了相同的保证,因此这是普遍适用的

-向下滚动到“内存障碍”部分(我会复制感兴趣的部分,但格式无法保存..)

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

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

如何防止读取在时间上向前移动(或 后续指令不被及时向后移动)保证 易变(新)读数?它有什么帮助

注意这里的术语。Volatile不是fresh的同义词。正如我前面提到的,它的真正用途在于如何将两个或多个Volatile读取链接在一起。Volatile读取序列中的下一次读取将绝对返回比相同地址的上一次读取更新的值。应该编写无锁代码记住这个前提。也就是说,代码的结构应该以处理较新值而不是最新值为原则。这就是为什么大多数无锁代码在循环中旋转,直到它能够验证操作是否完全成功

这本书中的观点(以及我个人的信仰)似乎 与C#的volatireRead和volatireWrite背后的思想相矛盾 实现


不完全是。记住volatile!=新鲜。是的,如果你想要“新鲜”读取然后您需要在读取之前放置一个获取围栏。但是,这与执行volatile读取不同。我要说的是,如果
volatireRead
的实现在读取指令之前调用了
Thread.MemoryBarrier
,那么它实际上不会产生volatile读取。if将产生新的read不过。

你在哪里读到的?@ThomasLevesque在这个问题的第一个答案的末尾:围栏的目的不是防止读取被缓存。它是防止以后的读取向后移动。@RaymondChen你是说一些处理器保证在获取围栏之前执行的读取将被缓存resh/immediate?你有任何来源吗?我似乎在任何地方都找不到。你在电子邮件中要求我对此帖子发表评论。我的评论是:当用户有这样的问题时,我会将他们推荐给像Raymond Chen或Joe Duffy这样的人。我个人不需要知道答案,因为幸运的是我从未需要编写依赖于Volatir的代码首先,我努力避免多线程。“完全有可能发生只进入缓存的易失性写操作”但是该方法确实保证了最新的值会被看到,并保证所有处理器都会立即看到该值。他们所做的只是在读取之后添加一个围栏,或者在写入之前添加一个围栏。我想我缺少的是:围栏为您提供了获取发布语义-但是如何实现呢