一尘不染

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

c#

在“坚果壳中的C#4”中,作者表明MemoryBarrier,即使没有,该类有时也可以写0 ,尽管我无法在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);
}

对我来说,这种需求似乎很疯狂。如何识别所有可能发生的情况?我认为,如果处理器更改了操作顺序,则需要确保行为不会更改。

您是否愿意使用壁垒?


阅读 587

收藏
2020-05-19

共1个答案

一尘不染

您将很难重现此错误。实际上,我什至会说您将永远无法使用.NET
Framework来重现它。原因是因为Microsoft的实现使用强大的内存模型进行写操作。这意味着将写入视为易失性。易失性写入具有锁定释放语义,这意味着必须在当前写入之前提交所有先前的写入。

但是,ECMA规范具有较弱的内存模型。因此,从理论上讲,Mono或什至是.NET Framework的将来版本都有可能开始表现出越野车行为。

因此,我要说的是,消除障碍#1和#2几乎不会对程序的行为产生任何影响。当然,这不是保证,而是仅基于当前CLR实施的观察结果。

消除3号和4号障碍肯定会产生影响。这实际上很容易复制。好吧,本质上不是这个示例,但是以下代码是更著名的演示之一。它必须使用Release版本进行编译并在调试器之外运行。错误是程序没有结束。您可以通过Thread.MemoryBarrierwhile循环内调用或标记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是最著名的示例,因为它会产生内存障碍。您可以通过在while循环中放置一个调用并观察该错误消失来验证这一点。

您可以在此处看到我的答案以对您引用的书中的示例进行其他分析。

2020-05-19