David762
David762

Reputation: 465

Use of volatile keyword on the flag field

I try to understand the use of volatile in multithread context. In the following code from another source of knowledge on the Internet:

class Program
{
    static string _result;
    //static volatile bool _done;
    static bool _done;

    static void SetVolatile()
    {
        // Set the string.
        _result = "Dot Net Perls";
        // The volatile field must be set at the end of this method.
        _done = true;
    }

    static void Main()
    {
        // Run the above method on a new thread.
        new Thread(new ThreadStart(SetVolatile)).Start();

        // Wait a while.
        Thread.Sleep(200);

        // Read the volatile field.
        if (_done)
        {
            Console.WriteLine(_result);
        }
    }
}

The demonstrated use of a volatile keyword should prevent a thread from reading a value stored in a cache. Instead of this it should check an actual value.

So without a volatile _done should still have a false value (read from the cache) and Console.WriteLine statement should not be executed.

Unfortunately running this code in Debug/Release mode without a volatile keyword always produces the output. What is the point of this particular example?

Upvotes: 2

Views: 306

Answers (2)

Valery Petrov
Valery Petrov

Reputation: 673

It's better to read ECMA-335, §I.12.6
Main points are:

  • Program can be optimized (a lot)
  • An optimizing compiler that converts CIL to native code shall not remove any volatile operation, nor shall it coalesce multiple volatile operations into a single operation


So, in this case, your code can be optimized.
Try the following code:

private bool flag = true;
public void LoopReadHoistingTest()
{
    Task.Run(() => { flag = false; });
    while (flag)
    {
      // Do nothing
    }
}

In Debug mode (without optimizations) it will work fine. In Release mode (with optimizations) it will hang forever, because moving the read outside the loop is quite common optimization.
But if you mark field volatile (or use Volatile.Read method, or some of Interlocked methods) it will work because in that case optimizations are prohibited.

In your example (without loop), Thread.Sleep makes an implicit memory barrier (because it's not forbidden and it makes code to work with less surprises), so it will read the value from memory. But I don't see any specification saying that it has to do an implicit memory barrier, so in some implementations it can be not true (or we have to find it in specification).

Upvotes: 0

Evk
Evk

Reputation: 101483

As already said, not using volatile keyword does not mean all reads will necessary be cached in all circumstances. They may be cached, or may be not. But if you want more reproducable example, try this:

class Program {
    static string _result;
    //static volatile bool _done;
    static bool _done;
    static void SetVolatile() {
        // Set the string.
        _result = "Dot Net Perls";
        // The volatile field must be set at the end of this method.
        _done = true;
    }

    static void Main() {
        // Run the above method on a new thread.
        new Thread(new ThreadStart(SetVolatile)).Start();

        // prevent compiler to throw away empty while loop
        // by doing something in it
        int i = 0;
        while (!_done) {
            i++;
        }
        Console.WriteLine("done " + i);
    }
}

Here you repeatedly read _done in a while loop, increasing possibility it will be cached. Program should terminate with "done" message but will not, because change to _done from another thread will not be noticed.

Upvotes: 3

Related Questions