38

C# 4 in a Nutshell(强烈推荐顺便说一句)使用以下代码来演示 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 和 4:我们不希望对_answer的写入进行优化并放置在写入_complete之后(障碍 1),我们需要确保_answer没有被缓存(障碍 4) . 我也认为我理解为什么需要屏障 3:如果 A 运行到刚刚写入_complete = true之后,B 仍然需要刷新_complete才能读取正确的值。

我不明白为什么我们需要屏障 2!我的一部分说这是因为线程 2(运行 B)可能已经运行到(但不包括)if(_complete),所以我们需要确保_complete被刷新。

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

4

2 回答 2

30

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

当然,这个例子并不能完全解决这个问题,因为A在写入之后什么都不做,_complete这意味着无论如何都会立即提交写入,因为线程提前终止了。

由于您所说的原因,您是否if仍然可以评估的问题的答案是肯定的。false但是,请注意作者在这一点上所说的话。

障碍 1 和 4 阻止此示例写入“0”。障碍 2 和 3 提供了新鲜度保证:它们确保如果 B 在 A 之后运行,读取 _complete 将评估为真。

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

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

于 2010-08-16T16:41:30.963 回答
3

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

  1. 完全显示栅栏发生的事情太简单了。
  2. Albahari 包括对非 x86 架构的要求。请参阅MSDN:“MemoryBarrier 仅在内存排序较弱的多处理器系统上需要(例如,使用多个 Intel Itanium 处理器 [Microsoft 不再支持] 的系统)。”。

如果您考虑以下内容,它会变得更加清晰:

  1. 内存屏障(此处为全屏障 - .Net 不提供半屏障)可防止读/写指令跳过栅栏(由于各种优化)。这保证了我们在栅栏之后的代码将在栅栏之前的代码之后执行。
  2. “这种序列化操作保证了在程序顺序中 MFENCE 指令之前的每个加载和存储指令都是全局可见的,然后 MFENCE 指令之后的任何加载或存储指令都是全局可见的。” 见这里
  3. x86 CPU 具有强大的内存模型,并保证写入对所有线程/内核都是一致的(因此在 x86 上不需要屏障 #2 和 #3)。但是,我们不能保证读取和写入将保持编码顺序,因此需要屏障#1 和#4。
  4. 内存屏障效率低下,不需要使用(参见同一篇 MSDN 文章)。我个人使用 Interlocked 和 volatile (确保你知道如何正确使用它!!),它们工作高效且易于理解。

附言。这篇文章很好地解释了 x86 的内部工作原理。

于 2017-02-27T09:14:35.150 回答