3

可能重复:
为什么我们需要 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. 据我了解,屏障在其以下指令之后阻止在其位置之前执行指令,我正确吗?
4

2 回答 2

6

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

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

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

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

于 2011-11-13T21:41:40.813 回答
1

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

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