尝试在 .NET 中捕获内存重新排序

Attempting to catch memory reordering in .NET

我有一个有点奇怪的程序,我试图在 .NET 中捕获一些内存重新排序:

private static volatile int x, y;
private static int b = -1;
private static int a = -1;

static void Main()
{
    Run(); 
}

public static void Run()
    {
        Random rand = new Random();
        var time = DateTime.Now;
        while ((a != 0 || b != 0))
        {
            int t1StartId = rand.Next(1, 10);
            int t2StartId = rand.Next(1, 10);

            Thread t1 = new Thread(RunOne);
            Thread t2 = new Thread(RunTwo);

            for (int i = 0; i <= 10; i++)
            {
                if (t1StartId == i)
                {
                    t1.Start();
                }

                if (t2StartId == i)
                {
                    t2.Start();
                }
            }

            t1.Join();
            t2.Join();
            x = 0;
            y = 0;
        }

        Console.WriteLine("Memory Reordered!!!!!!!!!!!!!!!!!!!!!");
        Console.WriteLine("Time Taken: {0}", DateTime.Now - time);
    }

    static void RunOne()
    {
        x = 1;
        a = y;
    }

    static void RunTwo()
    {
        y = 1;
        b = x;
    }
}

现在从我一直在做的阅读(here, here, here, here)来看,*应该有可能在每个线程完成执行后 a 和 b 都等于 0。有人能证实这一点吗?我的任何操作是否在我不知道的场景下出现了内存障碍,这会阻止重新排序,从而使无限循环继续下去?

我运行在 x86 CPU 上进行此操作,我怀疑这也会对结果产生影响。

编辑:

因此,在 运行 程序多次运行后,它现在正在退出循环:

我仍然不相信这是内存重新排序的结果。

第二个运行:

如此决定性的问题! - 这真的是内存重新排序的结果,还是其他原因?

是的,您的示例演示了内存重新排序。我无法自己复制它,但这可能是我特定设置的结果。

我将使用向下箭头 ↓ 表示易失性读取,使用向上箭头 ↑ 表示易失性写入。将箭头视为推开任何其他读取和写入。只要没有指令通过向下箭头向上和通过向上箭头向下,生成这些内存栅栏的代码就可以自由移动。然而,内存栅栏(箭头)被锁定在它们最初在代码中声明的位置。因此,使用图示的围栏重现您的代码将如下所示。

static void RunOne()
{
    ↑                 // volatile write fence
    x = 1;            
    var register = y; 
    ↓                 // volatile read fence
    a = register;
}

static void RunTwo()
{
    ↑                 // volatile write fence
    y = 1;
    var register = x;
    ↓                 // volatile read fence
    b = register;
}

所以你可以看到在RunOne中写入x和读取y是可以合法交换的。类似地,在 RunTwo 中,写入 y 和读取 x 也可以合法交换。

您使用 x86 体系结构这一事实对本示例没有影响,因为易失性写入后跟易失性读取是唯一仍然允许在 x86 的强内存模型中交换的安排。