Search code examples
c#multithreadingvolatilememory-barriers

Attempting to catch memory reordering in .NET


I have a slightly bizarre program in which I am attempting to catch some memory reordering in .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;
    }
}

Now from the reading I have been doing (here, here, here, here), it *should be possible for both a and b to equal 0 after each thread has completed executing. Is anyone able to confirm this? Are any of my operations presenting a memory barrier under the scenes of which I am not aware of which would prevent reordering and thus keep the infinite loop going?

I am running this on x86 CPU, which I suspect could also have impact on the result.

edit:

So after running the program multiple times it is now getting out of the loop:

enter image description here

I'm still not convinced this is a result of memory reordering.

Second run:

enter image description here

So conclusive question! - Is this truly a result of memory reordering, or is it something else?


Solution

  • Yes, your example demonstrates memory reordering. I could not reproduce it myself, but that's probably a result of my specific setup.

    I will use a down arrow ↓ to represent a volatile read and an up arrow ↑ to represent a volatile write. Think of the arrow head as pushing away any other reads and writes. The code that generates these memory fences is free to move around as long as no instruction goes up through a down arrow and down through an up arrow. The memory fences (the arrows), however, are locked in place at the spot where they were originally declared in the code. So reproducing your code with the fences illustrated would look like this.

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

    So you can see that in RunOne the write to x and the read of y can be swapped legally. Similarly in RunTwo the write y and the read of x can be swapped legally as well.

    The fact that you are using the x86 architecture has no implication on this example because a volatile write followed by a volatile read is the only arrangement still allowed to be swapped in the x86's strong memory model.