Threading – understanding the volatile modifier in C#

Yan Cui

I help clients go faster for less using serverless technologies.

This article is brought to you by

Don’t reinvent the patterns. Catalyst gives you consistent APIs for messaging, data, and workflow with key microservice patterns like circuit-breakers and retries for free.

Try the Catalyst beta

Whilst working on Bingo.Net I have come across a number of concurrency issues which must be fairly common in synchronous multiplayer games. During my attempts to overcome these obstacles I came across the volatile modifier in C# which at first glance looked like a silverbullet for all concurrency problems, but under closer inspection it more closely resembles a false dawn and is catered for a far more cornered use case…

Before we start, let’s first take a look at the definition of the volatile modifier on MSDN:

Fields that are declared volatile are not subject to compiler optimizations that assume access by a single thread. This ensures that the most up-to-date value is present in the field at all times.

In other words, by simply marking a field with the volatile keyword, we can be sure that we will always get the latest value of that field wherever 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 definition is correct it’s certainly misleading. What the volatile modifier gives you is protection against concurrency issues arising from multiple threads executing on multiple CPUs caching data and re-ordering instructions. Now let’s take a closer look at each case:

Data Caching

As Jon Skeet pointed out in his article (see references section), memory in modern computers is complex, with multiple levels of caching, processor registers and multiple processors sharing main memory but possibly not caches, etc. A processor might cache a piece of data from the main memory and use the cached data in the execution of a thread, modify it, and only update the main memory at a later time. During which time another thread running concurrently on another CPU might have read the same bit of data from main memory and used the outdated version of the data, i.e.:

image

Marking a field as volatile would make sure that it is not cached during the execution of a thread.

Compiler Optimization

The .NET compiler is allowed to alter the order of reads and writes in any way which does not change the meaning of the original program. For example, as pointed out in Dr Dobb’s articled (see reference section) it’s legal for the compiler to transform:

a = 1;  // A

a = 2;  // B

to:

// A’ – eliminate line A

a = 2;  // B

This is legal because the semantic of the original program is preserved. SImilarly:

a = 1;          // C – write to a

local = a;  // D – read from a

to

a = 1;          // C – write to a

local = 1;  // D’ – apply “constant propagation”

This reordering is also legal according to the rules of the compiler. However, it’s easy to see how this can be a problem in a multi-threaded environment where another thread is writing to a concurrently.

Pitfalls

It’s easy to be lured into a false sense of security by the volatile keyword and think that if you’re guarantee the latest value of a field you won’t need to use lock anymore. Marking a field as volatile doesn’t prevent multiple threads from interleaving , take this code for example:

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 modified on reference types, only the reference (which is a 32-bit integer pointing to a location in memory) itself is volatile but not the instance values. For example, you can’t use volatile on a double because it’s 64-bit and therefore read/write operations cannot be performed atomically (in a single instruction), but you can still make a wrapper 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 guaranteed the latest version of the reference pointer _volatileDouble, you’re not guaranteed the latest value of the Double property of the instance. To get around this, you will need to make the VolatileDouble type immutable so the value of its Double property cannot be changed without creating a new instance and updating the reference point _volatileDouble. However, in situations like this, it’s usually better and easier to use Interlocked.Read and Interlocked.Exchange in conjunction with BitConverter.Int64BitsToDouble and BitConverter.DoubleToInt64Bits instead.

Parting Thoughts..

Though the volatile modifier might not be the all-conquering solution to concurrency, 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 guaranteed to work, and we shouldn’t rely on the right combination of compiler, runtime, JIT, the CPU and the platform to ensure the correct running of our application. This is where the volatile modifier can help.

Also, the volatile modifier can help you achieve thread-safety in some situations. Unlike the bit of unsafe code I showed earlier, where you’re modifying the value of a field based on what it is right now, which requires you to lock around it. (and everywhere else you’re using the same field!) If all you want to do is do a check against a field and then perform an action which does not require using the field again then you can get away with just making 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 article on Volatility, Atomicity and Interlocking

Dr Dobb’s article on volatile in different languages

StackOverflow question on volatile vs Interlocked vs lock

StackOverflow question on using volatile and lock

StackOverflow question on using volatile on reference type holding a double

MSDN article on the volatile modifier

Whenever you’re ready, here are 3 ways I can help you:

  1. Production-Ready Serverless: Join 20+ AWS Heroes & Community Builders and 1000+ other students in levelling up your serverless game. This is your one-stop shop for quickly levelling up your serverless skills.
  2. I help clients launch product ideas, improve their development processes and upskill their teams. If you’d like to work together, then let’s get in touch.
  3. Join my community on Discord, ask questions, and join the discussion on all things AWS and Serverless.

6 thoughts on “Threading – understanding the volatile modifier in C#”

  1. Pingback: Threading — Producer-Consumer Pattern | theburningmonk.com

  2. Pingback: .Net Tips — using readonly vs const in C# | theburningmonk.com

  3. Also, volatile relies on using acquire fences on reads, and release fences on writes, which means that a write followed by a read may get swapped. Only a full fence with the lock statement or use of interlocked etc will guarantee the required behavior. Also most modern processors use such partial fences anyway, making the volatile directive redundant.

  4. Pingback: Resources for Microsoft Technologies | Insight's Delight

  5. 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!”);
    }
    as you have mentioned here that if we use the _Counter within if {} we can get a stale value but this contradicts as to what you have mentioned in the above daigram where each read and write happens from the register.

Leave a Comment

Your email address will not be published. Required fields are marked *