0

我以为我对这个主题了解很多,但以下内容让我感到困惑。我知道在我运行它的特定 PC 上,“_x++”被转换为三个汇编指令(将值从内存移动到寄存器,在寄存器中递增,写回内存)。那么,为什么在地球上,当锁定到单个 CPU 时,增加值的线程不会在增加值的确切时间被抢占,但仍然在寄存器中并且没有写回内存?有人知道这个问题的答案吗?我猜这完全靠运气,但我无法完全证明这一点。

通过在 VS 的反汇编窗口中设置断点inc eax,然后在调试器中执行几个步骤,我能够使正确的结果消失,但这不是证据。不是一个坚实的。

[此代码故意不是线程安全的。这不是生产代码,它用于教育目的。我正试图深入研究这个主题。当然,如果没有缓存线,我们会得到很多,但仍然—— 三个指令,甚至没有一个未命中,这很奇怪!这是一个 4 核 CPU,x64。]

{
    private static int NumberOfThreads = 2;
    private const long IncrementIterations = 1000000000;

    private static int _x;

    static void Main(string[] args)
    {
        Process.GetCurrentProcess().ProcessorAffinity = (IntPtr)1;

        Console.WriteLine("Hit Enter");
        Console.ReadLine();

        while (true)
        {
            var barrier = new Barrier(NumberOfThreads);

            _x = 0;

            var threads = new List<Thread>();

            for (int i = 0; i < NumberOfThreads; i++)
            {
                var thread = new Thread(
                    delegate()
                    {
                        barrier.SignalAndWait();

                        for (int j = 0; j < IncrementIterations; j++)
                        {
                            _x++;
                        }
                    });

                thread.Start();

                threads.Add(thread);
            }

            BlockUntilAllThreadsQuit(threads);

            Console.WriteLine(_x);

            Console.WriteLine("Actual increments: " + (IncrementIterations * NumberOfThreads));

            if (_x != (IncrementIterations * NumberOfThreads))
            {
                Console.WriteLine("Observed: " + _x);
                break;
            }
        }

        Console.ReadLine();
    }

    private static void BlockUntilAllThreadsQuit(IEnumerable<Thread> threadsToWaitFor)
    {
        foreach (var thread in threadsToWaitFor)
        {
            thread.Join();
        }
    }
}
4

1 回答 1

3

如果您使用带有 _x 字段的类,而不是使用被提升到闭包的 _x 变量,您的程序会更清晰。话虽如此,如果线程在执行增量时被抢占,增量将不会是原子的,特别是在加载完成和存储开始之间。那是一个非常小的窗口。某些任意事件可能会在那个确切时刻发生并触发任务切换,但可能性不大。请注意,运行循环所需的时间完全有可能使普通的计时器滴答事件始终错过神奇的机会窗口。

如果您想更好地测试事情,我建议您在循环中添加一些代码以随机化一点时间(例如,使用randThing初始化为 1 的真正自动变量,执行以下操作:

  如果 (randthing & 0x20000000)
    随机 ^= 0x20043251); // 从帽子中挑选出来的号码;一个原始多项式
                              // 会更好。
  别的
    随机 <<= 1;

可能会导致循环时序以非周期性方式从一次迭代到下一次迭代略有变化。

于 2012-06-18T22:35:23.673 回答