Threading — understanding the volatile modifier in C#

Whilst work­ing on Bingo.Net I have come across a num­ber of con­cur­ren­cy issues which must be fair­ly com­mon in syn­chro­nous mul­ti­play­er games. Dur­ing my attempts to over­come these obsta­cles I came across the volatile mod­i­fi­er in C# which at first glance looked like a sil­ver­bul­let for all con­cur­ren­cy prob­lems, but under clos­er inspec­tion it more close­ly resem­bles a false dawn and is catered for a far more cor­nered use case…

Before we start, let’s first take a look at the def­i­n­i­tion of the volatile mod­i­fi­er on MSDN:

Fields that are declared volatile are not sub­ject to com­pil­er opti­miza­tions that assume access by a sin­gle thread. This ensures that the most up-to-date val­ue is present in the field at all times.

In oth­er words, by sim­ply mark­ing a field with the volatile key­word, we can be sure that we will always get the lat­est val­ue of that field wher­ev­er we ask for it! Sounds too good to be true? That’s because it is.

The true face of the volatile modifier

Whilst MSDN’s def­i­n­i­tion is cor­rect it’s cer­tain­ly mis­lead­ing. What the volatile mod­i­fi­er gives you is pro­tec­tion against con­cur­ren­cy issues aris­ing from mul­ti­ple threads exe­cut­ing on mul­ti­ple CPUs caching data and re-order­ing instruc­tions. Now let’s take a clos­er look at each case:

Data Caching

As Jon Skeet point­ed out in his arti­cle (see ref­er­ences sec­tion), mem­o­ry in mod­ern com­put­ers is com­plex, with mul­ti­ple lev­els of caching, proces­sor reg­is­ters and mul­ti­ple proces­sors shar­ing main mem­o­ry but pos­si­bly not caches, etc. A proces­sor might cache a piece of data from the main mem­o­ry and use the cached data in the exe­cu­tion of a thread, mod­i­fy it, and only update the main mem­o­ry at a lat­er time. Dur­ing which time anoth­er thread run­ning con­cur­rent­ly on anoth­er CPU might have read the same bit of data from main mem­o­ry and used the out­dat­ed ver­sion of the data, i.e.:

image

Mark­ing a field as volatile would make sure that it is not cached dur­ing the exe­cu­tion of a thread.

Com­pil­er Opti­miza­tion

The .NET com­pil­er is allowed to alter the order of reads and writes in any way which does not change the mean­ing of the orig­i­nal pro­gram. For exam­ple, as point­ed out in Dr Dobb’s arti­cled (see ref­er­ence sec­tion) it’s legal for the com­pil­er to trans­form:

a = 1;  // A

a = 2;  // B

to:

// A’ – elim­i­nate line A

a = 2;  // B

This is legal because the seman­tic of the orig­i­nal pro­gram is pre­served. SIm­i­lar­ly:

a = 1;          // C – write to a

local = a;  // D – read from a

to

a = 1;          // C – write to a

local = 1;  // D’ – apply “con­stant prop­a­ga­tion”

This reorder­ing is also legal accord­ing to the rules of the com­pil­er. How­ev­er, it’s easy to see how this can be a prob­lem in a mul­ti-thread­ed envi­ron­ment where anoth­er thread is writ­ing to a con­cur­rent­ly.

Pitfalls

It’s easy to be lured into a false sense of secu­ri­ty by the volatile key­word and think that if you’re guar­an­tee the lat­est val­ue of a field you won’t need to use lock any­more. Mark­ing a field as volatile doesn’t pre­vent mul­ti­ple threads from inter­leav­ing , take this code for exam­ple:

private volatile int _counter = 0;

if (_counter < 10) // guaranteed latest value of _counter
{
    // value of _counter could have been modified by another thread,
    // hence invalidating the if condition
    _counter++;
}

Also, although you’re able to use the volatile mod­i­fied on ref­er­ence types, only the ref­er­ence (which is a 32-bit inte­ger point­ing to a loca­tion in mem­o­ry) itself is volatile but not the instance val­ues. For exam­ple, you can’t use volatile on a dou­ble because it’s 64-bit and there­fore read/write oper­a­tions can­not be per­formed atom­i­cal­ly (in a sin­gle instruc­tion), but you can still make a wrap­per class volatile:

public class MyClass
{
    private volatile VolatileDouble _volatileDouble; // guaranteed
}

public class VolatileDouble
{
    public double Double { get; set; }  // not guaranteed
}

In this case, although you’re guar­an­teed the lat­est ver­sion of the ref­er­ence point­er _volatileDouble, you’re not guar­an­teed the lat­est val­ue of the Dou­ble prop­er­ty of the instance. To get around this, you will need to make the Volatile­Dou­ble type immutable so the val­ue of its Dou­ble prop­er­ty can­not be changed with­out cre­at­ing a new instance and updat­ing the ref­er­ence point _volatile­Dou­ble. How­ev­er, in sit­u­a­tions like this, it’s usu­al­ly bet­ter and eas­i­er to use Interlocked.Read and Interlocked.Exchange in con­junc­tion with BitConverter.Int64BitsToDouble and BitConverter.DoubleToInt64Bits instead.

Parting Thoughts..

Though the volatile mod­i­fi­er might not be the all-con­quer­ing solu­tion to con­cur­ren­cy, the point to take away from here is that although code like this works most of the time:

class BackgroundTaskDemo
{
    private bool stopping = false;

    static void Main()
    {
        BackgroundTaskDemo demo = new BackgroundTaskDemo();
        new Thread(demo.DoWork).Start();
        Thread.Sleep(5000);
        demo.stopping = true;
    }

    static void DoWork()
    {
        while (!stopping)
        {
            // Do something here
        }
    }
}

it’s not guar­an­teed to work, and we shouldn’t rely on the right com­bi­na­tion of com­pil­er, run­time, JIT, the CPU and the plat­form to ensure the cor­rect run­ning of our appli­ca­tion. This is where the volatile mod­i­fi­er can help.

Also, the volatile mod­i­fi­er can help you achieve thread-safe­ty in some sit­u­a­tions. Unlike the bit of unsafe code I showed ear­li­er, where you’re mod­i­fy­ing the val­ue of a field based on what it is right now, which requires you to lock around it. (and every­where else you’re using the same field!) If all you want to do is do a check against a field and then per­form an action which does not require using the field again then you can get away with just mak­ing it volatile:

private volatile int _counter = 0;
if (_counter < 10) // guaranteed latest value of _counter
{
    // you're safe so long you don't reference _counter again in here, that
    // goes to any method you call from here too!
    Console.WriteLine("We're still not there yet!");
}

References:

Jon Skeet’s arti­cle on Volatil­i­ty, Atom­ic­i­ty and Inter­lock­ing

Dr Dobb’s arti­cle on volatile in dif­fer­ent lan­guages

Stack­Over­flow ques­tion on volatile vs Inter­locked vs lock

Stack­Over­flow ques­tion on using volatile and lock

Stack­Over­flow ques­tion on using volatile on ref­er­ence type hold­ing a dou­ble

MSDN arti­cle on the volatile mod­i­fi­er