Search code examples
c#.netmultithreadingvolatile

Forcing a .NET, multithreaded volatile optimization bug


I'm trying to reproduce the bug described in CLR via C#. When the following code is compiled with optimizations the s_stopWorker variable is only checked once (and is false) so the application never terminates.

private static bool s_stopWorker;

public static void Main()
{
    Console.WriteLine("Main: letting worker run for 5 seconds");
    var t = new Thread(Worker);
    t.Start();
    Thread.Sleep(5000);
    s_stopWorker = true;
    Console.WriteLine("Main: waiting for worker to stop");
    t.Join();
}

private static void Worker(object o)
{
    var x = 0;
    while (!s_stopWorker) x++;
    Console.WriteLine("Worker: stopped when x={0}", x);
}

This is indeed what is happening (both on x86 and x64, contrary to the book).

If I put a Console.Write in the while then suddenly the optimization doesn't happen anymore.

private static bool s_stopWorker;

public static void Main()
{
    Console.WriteLine("Main: letting worker run for 5 seconds");
    var t = new Thread(Worker);
    t.Start();
    Thread.Sleep(5000);
    s_stopWorker = true;
    Console.WriteLine("Main: waiting for worker to stop");
    t.Join();
}

private static void Worker(object o)
{
    var x = 0;
    while (!s_stopWorker)
    {
        Console.Write(string.Empty);  // <-- Added line
        x++;
    }
    Console.WriteLine("Worker: stopped when x={0}", x);
}

Now the bug has vanished and the application exits as if the optimization hadn't happened.

Main: letting worker run for 5 seconds
Main: waiting for worker to stop
Worker: stopped when x=130084144

Why does the addition of the Console.Write fix this bug?


Solution

  • The code inside of Console.Write could change the value of the static field. It does not but the JIT does not know that. So it must generated code to load the static field each iteration.

    You could achieve the same effect by calling an empty method that has the no inlining flag set. This is a black box to the JIT.

    The JIT could analyze all code that might be called transitively and conclude, that the static field will not change. But this is not implemented in the JIT.