Monday, 12 March 2007

The Singleton pattern represents a very common way of using lazy initialization with resources (e.g. for initialising a Genome DataDomain). Textbooks usually describe the Singleton as follows:

public class SingletonClass
{
  private static SingletonClass instance;
  public static SingletonClass Instance
  {
    get {
      if (instance == null)
      {
        instance = new SingletonClass(…);
        // do some initialization logic
      }
      return instance;
    }
  }
...
}

However, as any developer quickly realizes - implementing it in this way in a multi-threaded scenario is highly error-prone: if two threads attempt to access Instance at the same time (when it hasn't been created yet), both may run inside the if block and one of the threads will override the instance created by the other. As we tried to defend ourselves against this scenario, we changed the implementation as follows:

public class SingletonClass
{
  private static object syncObj = new object()
 
  private static SingletonClass instance;
  public static SingletonClass Instance
  {
    get {
      if (instance == null)
      {
        lock(syncObj)
        {
          if (instance == null)
          {
            instance = new SingletonClass(…);
            // do some initialization logic
          }
        }
       }
       return instance;
     }
  }
}

It looks nice and safe now, doesn't it? We thought so and used this solution whenever we needed to block other threads from accessing an object that was just being constructed.

And, of course, we were wrong.

This approach would only work if we didn't run any additional initialization logic after creating the instance. If this is not the case, other threads that just step into this execution block find that the object reference is already set when they check whether the instance is null, and go on using an object that's not yet initialized fully.

So our final solution was to make setting instance the last command inside the inner if block:

if (instance == null)
{
  SingletonClass initializingInstance = 
  new SingletonClass(…);
  // do some initialization logic 
  // on initializingInstance
 
  instance = initializingInstance;
}

Well, not bad so far, it just needs a minor check to see if this solution is valid. A quick glance into the documentation confirmed that setting a reference is an atomic operation, so multi-threading issues should not affect it. Theoretically this should be the case, but as it turned out after googling a bit, the CLR memory model allows reads and writes to be reordered as long as the operation doesn't affect the behavior of the thread executing the code (regarding to my knowledge this happens only in 64Bit environment). A-ha, but we have more than one thread now...

Let's look at an example to see what this means. If the "initialization logic" mentioned in the code is:

initializingInstance.someStringField = "some string value";

and after acquiring the instance, we use it as:

Console.WriteLine(SingletonClass.Instance.someStringField.ToLower());

the compiler may optimize the code such that the string value is set just before accessing the field. But when another thread gets to this execution point, this string reference may still be null for that thread.

Actually this is when the volatile keyword steps into view: declaring instance volatile makes all access to the variable multithread-safe. But doing this would be overkill, as it prevents optimization for all reads and writes to the variable, not just where we really need it to behave so. Since .NET 2.0, we have an API call that 'flushes' all memory operations preceding its call, namely Thread.MemoryBarrier():

if (instance == null)
{
  SingletonClass initializingInstance = 
  new SingletonClass(…);
  // do some initialization logic 
  // on initializingInstance
 
  System.Threading.Thread.MemoryBarrier();
  instance = initializingInstance;
}

Pretty, isn't it?

And still, this is just the simplest case, as the locking condition (instance == null) and the operation that may alter it (instance = initializingInstance) are atomic, thread-safe operations.

There are more complex manifestations of this problem – not when implementing singletons, but other kinds of lazy initialization. In those cases however, when these preconditions are not always true or one is considering using additional locking strategies (around the altering operation, using read/write locks or even locking the whole operation), the core of the implementation usually remains the same.

Posted by Attila.

Monday, 12 March 2007 17:57:28 (W. Europe Standard Time, UTC+01:00)  #    Disclaimer  |  Comments [3]  |  Tracked by:
"http://9ujnm-le-informazioni.cn/52058967/copriletti-all-uncinetto.html" (http:/... [Pingback]

Monday, 12 March 2007 19:10:51 (W. Europe Standard Time, UTC+01:00)
Thought you might be interested in this article: http://www.yoda.arachsys.com/csharp/singleton.html. An interesting read on singletons in .NET.
Tuesday, 13 March 2007 03:24:27 (W. Europe Standard Time, UTC+01:00)
Yes, Good article. I also prefer:

public sealed class Singleton
{
Singleton()
{
}

public static Singleton Instance
{
get
{
return Nested.instance;
}
}

class Nested
{
// Explicit static constructor to tell C# compiler
// not to mark type as beforefieldinit
static Nested()
{
}

internal static readonly Singleton instance = new Singleton();
}
}

Thursday, 15 March 2007 13:21:03 (W. Europe Standard Time, UTC+01:00)
Hi guys,

I didn’t limit the scope of the synchronisation to singleton, but I chose singleton because it's a simple example that everyone is aware of.

I'm going to investigate the alternative solutions for singletons that you posted now and I'll follow up with comments on that soon...

Thank you for your feedback!
Attila
Comments are closed.