【问题标题】:Need clarification about Thread.MemoryBarrier() [duplicate]需要澄清 Thread.MemoryBarrier() [重复]
【发布时间】:2011-12-28 05:15:42
【问题描述】:

可能重复:
Why we need Thread.MemoryBarrier()?

简而言之,来自 O'Reilly 的 C#:

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);
        }
    }
}

假设方法 A 和 B 在不同的线程上同时运行:


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

我的问题是:

  1. 为什么需要屏障 4?障碍 1 还不够吗?
  2. 为什么需要 2 和 3?
  3. 据我了解,屏障在其后续指令之后阻止在其位置之前执行指令,我是否正确?

【问题讨论】:

  • 对您有益的主要说明:永远不要编写需要任何这些的代码。使用lockInterLocked 和其他标准类。
  • 它更像是stackoverflow.com/questions/3493931/… 的副本,而不是链接的那个。

标签: c# multithreading memory-barriers


【解决方案1】:

内存屏障对从/到内存的读取和写入强制执行顺序约束:在屏障之前的内存访问操作发生在屏障之后的内存访问之前。

  1. 屏障 1 和 4 具有互补作用:屏障 1 确保 写入_answer 发生在写入 _complete 之前,而屏障 4 确保 _complete 读取发生在从_answer 读取之前。想象一下障碍 4 不存在,但障碍 1 存在。虽然可以保证123true 被写入_complete 之前被写入_answer,但运行B() 的其他线程可能仍然对其读取操作进行重新排序,因此它可能在读取@ 之前读取_answer 987654331@。类似地,如果屏障 1 被移除而屏障 4 被保留:而在 B() 中从 _complete 的读取将始终发生在从 _answer 读取之前,_complete 仍然可以在 _answer 之前被其他线程写入运行A()

  2. 屏障 2 和 3 提供新鲜度保证:如果屏障 3 在屏障 2 之后执行,则在执行屏障 2 时对运行 A() 的线程可见的状态对运行 B() 的线程可见在它执行屏障 3 时。如果没有这两个屏障中的任何一个 B()A() 完成后执行,可能看不到 A() 所做的更改。特别是屏障 2 防止写入_complete 的值被运行A() 的处理器缓存,并强制处理器将其写入主存储器。类似地,屏障 3 阻止运行 B() 的处理器依赖缓存来获取 _complete 的值,从而强制从主内存读取。但是请注意,在没有内存屏障 2 和 3 的情况下,陈旧的缓存并不是唯一可以防止新鲜度保证的东西。内存总线上的操作重新排序是这种机制的另一个示例。

  3. 内存屏障只是确保内存访问操作的效果是跨屏障排序的。其他指令(例如,增加寄存器中的值)可能仍会重新排序。

【讨论】:

  • 我认为屏障 2 主要用于确保 true 实际写入 _complete。如果它不存在,编译器可以在一段时间内将处理器寄存器中的值兑现。这一点尤其重要,因为函数 A 和 B 非常简单,它们很可能被内联到调用方法中。同样,需要屏障 3 来确保 _complete 从内存中重新读取,并且不使用寄存器兑现副本。
  • 谢谢,马丁!我已经更新了我的帖子。
  • 谢谢,但是有两件事:首先,以确保:需要屏障 4,以防 CPU 缓存 _answer,甚至在 B 中的执行路径之前是已知的(因为WriteLineif 分支内)? 第二:我认为你很困惑 - 如果不是,请纠正我,以确保不是我的困惑 - 使用:“......虽然可以保证 true 在 123 之前写入 _complete写给 _answer...”,我相信你的意思是相反的:“...虽然可以保证 123 被写入 _answer,然后 true 被写入 _complete...”。
  • 第一:没错。第二:确实。我已经纠正了错误。
【解决方案2】:

好的,我们开始: 内存屏障防止优化编译器重新排序指令。这意味着在屏障之前的指令不能在屏障之后的指令之后执行。有几种类型的障碍,但我不会详细介绍。此外,内存排序较弱的 CPU 可以重新排序指令并可能产生死锁。所以:

  1. 需要屏障 4 才能使线程运行方法 B 读取 _answer 的最新值(即读取 123 而不是 0)。如果您在 Release 模式下编译,编译器会优化代码并重新排序指令,这样运行 B 的线程就有可能读取 0,即使您编写的指令在逻辑上会使这不可能(因为 _answer 是在 _complete 之前分配)。
  2. 障碍 2 和 3 还阻止重新排序(以及缓存 _complete 的值),因此运行 B 的线程不可能将 _complete 读为 false,只要它在 A 之后运行。
  3. 答案在上面。

【讨论】:

    猜你喜欢
    • 2012-07-05
    • 2011-08-23
    • 1970-01-01
    • 1970-01-01
    • 2011-11-09
    • 2012-07-18
    • 1970-01-01
    • 2023-03-09
    • 1970-01-01
    相关资源
    最近更新 更多