需要澄清Thread.MemoryBarrier()

时间:2011-11-13 21:00:06

标签: c# multithreading memory-barriers

  

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

来自O'Reilly的C#in a Nutshell:

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. 为什么需要Barrier 4?障碍1是不够的?
  2. 为什么2&需要3个?
  3. 根据我的理解,屏障阻止在按照以下说明操作之前执行指令,我是否正确?

2 个答案:

答案 0 :(得分:6)

内存屏障强制对来自内存的读写进行排序约束:在屏障发生之前的内存访问操作 - 在屏障之后的内存访问之前。

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

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

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

答案 1 :(得分:1)

好的,我们走了: 内存屏障阻止优化编译器重新排序指令。这意味着在屏障之后的指令之后,不能在屏障之前执行指令。有几种类型的障碍,但我不会详述。此外,具有弱内存排序的CPU可以重新排序指令并可以创建死锁。所以:

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