为什么我需要内存屏障?

时间:2010-08-16 14:10:58

标签: c# multithreading thread-safety shared-memory memory-barriers

Nutshell中的C#4(强烈推荐的btw)使用以下代码来演示MemoryBarrier的概念(假设A和B在不同的线程上运行):

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);
    }
  }
}
他们提到障碍1& 4防止这个例子写0和障碍2& 3提供新鲜度保证:他们确保如果B在A之后运行,则读取 _complete 将评估为 true

我不是真的得到它。我想我理解为什么障碍1&amp; 4是必要的:我们不希望在写入 _complete (障碍1)之后优化并放置写入 _answer ,我们需要确保 _answer 未缓存(障碍4)。我也认为我理解为什么Barrier 3是必要的:如果A在写完 _complete = true 之后才运行,B仍然需要刷新 _complete 来读取正确的值。< / p>

我不明白为什么我们需要障碍2!我的一部分说这是因为也许线程2(运行B)已经运行直到(但不包括) if(_complete),因此我们需要确保刷新 _complete

但是,我不知道这有多大帮助。是否仍然可以在A中将 _complete 设置为true,但是B方法会看到 _complete 的缓存(错误)版本?即,如果线程2运行方法B直到第一个MemoryBarrier之后,然后线程1运行方法A直到 _complete = true 但没有进一步,然后线程1恢复并测试 if(_complete)< / strong> - 如果不能导致错误

2 个答案:

答案 0 :(得分:26)

屏障#2保证对_complete的写入立即生效。否则,它可能会保持排队状态,这意味着即使_complete有效地使用了易失性读取,BA的读取也看不到由B引起的更改。

当然,这个例子并不能完全解决这个问题,因为A在写入_complete之后不再做任何事情,这意味着自从线程提前终止以来,无论如何都会立即进行写入。 / p>

关于if仍然可以评估为false的问题的答案是肯定的,原因正如您所述。但是,请注意作者关于这一点的说法。

  

障碍1和4阻止了这个例子   写“0”。障碍2和3   提供新鲜度保证:他们   确保如果B在A 之后运行,则读取   _complete将评估为true。

强调“如果B跑过A”是我的。当然可能是两个线程交错的情况。但是,作者忽略了这种情况,大概是为了说明Thread.MemoryBarrier如何更简单地发挥作用。

顺便说一句,我很难在我的机器上设计一个示例,其中障碍#1和#2会改变程序的行为。这是因为关于写入的内存模型在我的环境中很强。也许,如果我有一台多处理器机器,使用Mono,或者有一些其他不同的设置我可以证明它。当然,很容易证明消除障碍#3和#4会产生影响。

答案 1 :(得分:2)

这个例子不清楚有两个原因:

  1. 完全展示围栏发生的事情太简单了。
  2. Albahari包含非x86架构的要求。请参阅MSDN:“仅在内存排序较弱的多处理器系统上需要MemoryBarrier(例如,采用多个Intel Itanium处理器的系统[Microsoft不再支持])。”。
  3. 如果您考虑以下因素,则会更清楚:

    1. 内存屏障(此处为完全屏障 - .Net不提供半屏障)可防止读/写指令跳过屏障(由于各种优化)。这保证了我们在围栏之后执行代码之后的代码。
    2. “此序列化操作保证在MFENCE指令之后的任何加载或存储指令全局可见之前,MFENCE指令在程序顺序之前的每个加载和存储指令都是全局可见的。”见here
    3. x86 CPU具有强大的内存模型,并保证写入与所有线程/核心一致(因此x86上不需要障碍#2和#3)。但是,我们无法保证读写将保持编码顺序,因此需要屏障#1和#4。
    4. 内存障碍效率低下,无需使用(请参阅相同的MSDN文章)。我个人使用Interlocked和volatile(确保你知道如何正确使用它!),它有效且易于理解。
    5. 聚苯乙烯。 This article很好地解释了x86的内部工作原理。