Access Reordering and Volatile in C#

To avoid the overhead of synchronization, some programmers will build their own optimiza­tion primitives. In C#, however, some surprising subtleties exist in what the language and runtime guarantee with respect to instruction ordering, especially to those who are familiar with the x86 architecture, which doesn’t typically perform these operations.

This topic is complex, but it isn’t necessary to fully understand it if you stick to the synchronization methods discussed earlier in this chapter.

To illustrate this, consider the following example:

using System;

using System.Threading;

class Problem {

int x; int y; int curx; int cury;

public Problem()

{

x = 0;

y = 0;

}

public void Process1()

{

x = 1;

cury = y;

}

public void Process2()

{

y = 1;

curx = x;

}

public void TestCurrent()

{

Console.WriteLine(“curx, cury: {0} {1}”, curx, cury);

}

}

class Test {

public static void Main()

{

Problem p = new Problem();

Thread t1 = new Thread(new ThreadStart(p.Process1));

Thread t2 = new Thread(new ThreadStart(p.Process2));

t1.Start();

t2.Start();

t1.Join();

t2.Join();

p.TestCurrent();

}

}

What possible values can be printed for curx and cury? It’s not surprising that the following are two possible values:

curx, cury: 1 0

curx, cury: 0 1

This makes sense from the serial nature of the code in Processl() and Process2(); either function can complete before the other one starts.

The following output is a bit less obvious:

curx, cury: 1 1

This is a possibility because one of the threads could be interrupted after the first instruc­tion and the other thread could run.

The point of the example, however, is that there’s a fourth possible output:

curx, cury: 0 0

This happens because of one of those things that’s assumed to be true but isn’t really always true. The common assumption when looking at the code in Process1() is that the lines always execute in the order in which they’re written. Surprisingly, that isn’t true; the following are a few cases where the instructions might execute out of order:

  • The compiler could choose to reorder the statements, as there’s no way for the compiler to know this isn’t safe.
  • The JIT could decide to load the values for both x and y into registers before executing either line of code.
  • The processor could reorder the execution of the instructions to be faster.[1]
  • On a multiprocessor system, the values might not be synchronized in global memory.

What’s needed to address this is a way to annotate a field so that such optimizations are inhibited. C# does this with the volatile keyword.

When a field is marked as volatile, reordering of instructions is inhibited, so the following is true:

  • A write can’t be moved forward across a volatile write.
  • A read can’t be moved backward across a volatile read.

In the example, if curx and cury are marked volatile, the code in Processl() and Process2() can’t be reordered:

public void Processl()

{

x = 1;

cury = y;

}

Since cury is now volatile, the write to x can’t be moved after the write to cury.

In addition to precluding such reordering, volatile also means that the JIT can’t keep the variable in the register and that the variable must be stored in global memory on a multi­processor system.

So what’s volatile good for, when you already have ways of doing synchronization?

1. Using volatile

You can use volatile to implement a thread-safe version of a singleton class. The traditional implementation uses lock:

using System; class Singleton {

static object sync = new object();

static Singleton singleton = null;

private Singleton()

{

}

public static Singleton GetSingleton()

{

lock(sync)

{

if (singleton == null)

singleton = new Singleton();

return(singleton);

}

}

}

This works fine, but it’s pretty wasteful; the synchronization is really needed only the first time the function is called. In this case, the lock needs to be on a static variable because the function is a static function.

With volatile, you can write a nicer version: using System;

class Singleton {

static object sync = new object();

static volatile Singleton singleton = null;

private Singleton()

{

}

public static Singleton GetSingleton()

{

if (singleton == null)

{

lock(sync)

{

//check for null again in case another thread has assigned

//a value between the previous check and the lock statement if (singleton == null)

singleton = new Singleton();

}

}

return(singleton);

}

}

This version has much better performance since the synchronization is required only if the object hasn’t been created.

Source: Gunnerson Eric, Wienholt Nick (2005), A Programmer’s Introduction to C# 2.0, Apress; 3rd edition.

Leave a Reply

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