代码之家  ›  专栏  ›  技术社区  ›  Felipe Pessoto

为什么我们需要Thread.MemoryBarrier内存()?

  •  47
  • Felipe Pessoto  · 技术社区  · 15 年前

    MemoryBarrier ,尽管我无法在Core2Duo中复制:

    public class Foo
    {
        int _answer;
        bool _complete;
        public void A()
        {
            _answer = 123;
            //Thread.MemoryBarrier();    // Barrier 1
            _complete = true;
            //Thread.MemoryBarrier();    // Barrier 2
        }
        public void B()
        {
            //Thread.MemoryBarrier();    // Barrier 3
            if (_complete)
            {
                //Thread.MemoryBarrier();       // Barrier 4
                Console.WriteLine(_answer);
            }
        }
    }
    
    private static void ThreadInverteOrdemComandos()
    {
        Foo obj = new Foo();
    
        Task.Factory.StartNew(obj.A);
        Task.Factory.StartNew(obj.B);
    
        Thread.Sleep(10);
    }
    

    你费心使用障碍物吗?

    6 回复  |  直到 13 年前
        1
  •  70
  •   Community Mohan Dere    9 年前

    你将很难复制这个错误。事实上,我想说的是,您将永远无法使用.NET框架复制它。原因是微软的实现使用了一个强大的内存模型来进行写操作。这意味着写操作被当作是不稳定的。volatile写入具有锁释放语义,这意味着所有先前的写入必须在当前写入之前提交。

    然而,ECMA规范有一个较弱的内存模型。因此,从理论上讲,Mono甚至未来版本的.netframework可能会开始出现这种错误行为。

    拆除障碍物3和4肯定会产生影响。这其实很容易复制。好吧,不是这个例子本身,但下面的代码是一个更知名的演示。它必须使用发行版编译并在调试器外部运行。问题是程序没有结束。您可以通过调用 Thread.MemoryBarrier while 循环或标记 stop volatile .

    class Program
    {
        static bool stop = false;
    
        public static void Main(string[] args)
        {
            var t = new Thread(() =>
            {
                Console.WriteLine("thread begin");
                bool toggle = false;
                while (!stop)
                {
                    toggle = !toggle;
                }
                Console.WriteLine("thread end");
            });
            t.Start();
            Thread.Sleep(1000);
            stop = true;
            Console.WriteLine("stop = true");
            Console.WriteLine("waiting...");
            t.Join();
        }
    }
    

    一些线程错误很难重现的原因是,模拟线程交错的相同策略实际上可以修复错误。 Thread.Sleep 循环并观察bug是否消失。

    你可以看到我的答案 here

        2
  •  10
  •   Hans Passant    15 年前

    可能性是 很好,第一个任务在第二个任务开始运行时就完成了。只有当两个线程同时运行代码并且没有中间的缓存同步操作时,才能观察到这种行为。在您的代码中有一个,StartNew()方法将在线程池管理器的某个地方获取一个锁。

    让两个线程同时运行此代码是非常困难的 非常 不要

    远离这一点,使用lock语句来编写合理的多线程代码。

        3
  •  2
  •   Steven Sudit    15 年前

    volatile lock ,内存屏障内置。但是,是的,你确实需要它。话虽如此,我怀疑你需要的数量是你的例子的一半。

        4
  •  2
  •   Grzenio    15 年前

    复制多线程错误非常困难-通常您必须多次(数千次)运行测试代码,并进行一些自动检查,以便在错误发生时进行标记。你可以试着加一个短的线程。睡眠(10) 在一些线之间,但它并不总是保证你会得到相同的问题没有它。

        5
  •  1
  •   dsolimano    15 年前

    如果您接触过来自两个不同线程的数据,可能会发生这种情况。这是处理器用来提高速度的技巧之一——你可以构建没有这样做的处理器,但是它们会慢得多,所以没有人再这样做了。你应该读一些类似的东西 Hennessey and Patterson 认识到各种各样的比赛条件。

    我总是使用某种更高级别的工具,比如监视器或锁,但在内部,它们也在做类似的事情,或者是用屏障实现的。

        6
  •  1
  •   Łukasz Szkup    8 年前

    我将引用一篇关于多线程的伟大文章:

    考虑以下示例:

    class Foo
    {
      int _answer;
      bool _complete;
    
      void A()
      {
        _answer = 123;
        _complete = true;
      }
    
      void B()
      {
        if (_complete) Console.WriteLine (_answer);
      }
    }
    

    如果方法A和B在不同的线程上同时运行,可能是这样的吗 B是否可能写入0?下面的答案是肯定的 原因:

    提高效率。编译器、CLR或CPU可能会引入缓存 其他线程马上。C#和运行时非常小心地 确保这样的优化不会破坏普通的单线程 正确使用锁的代码或多线程代码。外面 在这些场景中,您必须通过 创建内存屏障(也称为内存围栏)来限制 指令重新排序和读写缓存的影响。

    全栅栏

    最简单的记忆障碍是完全记忆 阻止任何指令重新排序的屏障(全栅栏) 或者围着栅栏。打电话Thread.MemoryBarrier内存生成 跟随:

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

    Thread.MemoryBarrier http://www.albahari.com/threading/part4.aspx

    推荐文章