Search code examples
c#.netmultithreadingvolatile

C# compiler optimization and volatile keyword


I have read some posts about volatile keyword and behaviour without this keyword.

I've especially tested the code from the answer to Illustrating usage of the volatile keyword in C#. When running, I observe the excepted behaviour in Release mode, without debugger attached. Up to that point, there is no problem.

So, as far as I understand, the following code should never exit.

public class Program
{
    private bool stopThread;

    public void Test()
    {
        while (!stopThread) { }  // Read stopThread which is not marked as volatile
        Console.WriteLine("Stopped.");
    }


    private static void Main()
    {
        Program program = new Program();

        Thread thread = new Thread(program.Test);
        thread.Start();

        Console.WriteLine("Press a key to stop the thread.");
        Console.ReadKey();

        Console.WriteLine("Waiting for thread.");
        program.stopThread = true;

        thread.Join();  // Waits for the thread to stop.
    }
}

Why does it exit? Even in Release mode, without debugger?

Update

An adaptation of the code from Illustrating usage of the volatile keyword in C#.

private bool exit;

public void Test()
{
    Thread.Sleep(500);
    exit = true;
    Console.WriteLine("Exit requested.");
}

private static void Main()
{
    Program program = new Program();

    // Starts the thread
    Thread thread = new Thread(program.Test);
    thread.Start();

    Console.WriteLine("Waiting for thread.");
    while (!program.exit) { }
}

This program does not exit after in Release mode, without debugger attached.


Solution

  • So, as far as I understand, the following should never exit.

    No, it can stop. It just isn't guaranteed to.

    It doesn't stop on the machine I'm currently running on, for example - but equally I could try the exact same executable on another machine and it might behave fine. It will depend on the exact memory model semantics used by the CLR it runs on. That will be affected by the underlying architecture and potentially even the exact CPU being used.

    It's important to note that it's not the C# compiler which determines what to do with a volatile field - the C# compiler just indicates the volatility in the metadata using System.Runtime.CompilerServices.IsVolatile. Then the JIT can work out what that means in terms of obeying the relevant contracts.