Wednesday, March 19, 2008

Double-Checked Locking Pattern

Scope

This post is meant to discuss the overview of the Double-Checked Locking pattern, and discuss any alternative means to address te thread-safety issue with the Singleton pattern. Its presumed that we all are aligned as to what the singleton pattern is all about.

Introduction

Double-Checked Locking is a pattern in its own sense which is designed to add efficient thread safety to initialization of shared resources. It saves us of the overhead associated with applying various synchronization techniques to atomize the initialization of these shared resources. An example would be Gamma Singleton, where to have synchronized creation of the single instance of an object we need to acquire a lock before proceeding to initialize. The locking though is an overhead each time except when the resource is actually being initialized. The DCLP optimization proposes to check the lock hint (locking condition) in an unsafe way before proceeding to lock

The pattern itself is not perfect and might fail for varied reasons on uniprocessor or multiprocessor architectures. In the remaining article we'll discuss the attempt of DCLP to make a Singleton thread safe, concluding with a mechanism to address the issue.

Issue with Gamma Singleton

The traditional Gamma approach to implement a Singleton is based on intializing a static pointer on heap on its first access, and returning the exisiting instance if already created. This is done by checking the instance member say mInstanceP for NULLness before actual creation. This suffices in most single threaded environments, but then you might bump into interrupts or multi-threaded access to your singleton instance. Making the access thread safe seems to be relatively easy. All you need to do is to acquire a lock before testing for nullness, but this would be pretty expensive for each access to your singleton as acquiring locks is a heavy operation. DLCP comes to your rescue here.

Double-Checked Locking Pattern in action

The DCLP proposes to resolve the problem faced by the above method by first checking a lock hint/condition which determines whether our singleton is already setup, if not then proceed to aquire lock. It then again checks for the condition so as to initialize the instance and the conditional variable. The second condition here is essential to prevent the race condition where multiple threads execute in parallel. The threads will queue up at lock and when they finally acquire the lock they'll find the instance as already initialized, hence will skip the same.

class Singleton
{
public:
static Singleton *getInstance (void)
{
// First check
if (mInstanceP == 0) {
// Ensure serialization
// (guard constructor acquires mLock)
Guard guard (mLock);
// Double check.
if (mInstanceP == 0)
mInstanceP = new Singleton;
}
return mInstanceP;
// guard destructor releases mLock.
}

private:

static Mutex mLock;
static Singleton *mInstanceP;
};
Though this solution appears to be optimal one, but in certain circumstances is not reliable. In the following section we'll discuss these issues and address what we can.

Where the Double-Checked Locking pattern gives way!

There are several scenarios in which the DCLP might not behave in accordance to the 'observable behavior' mandated by the language standards.
Consider the initialization of the singleton instance using the expression
mInstanceP = new Singleton;
The compilers internally are free to breakup the expression into steps/instructions. The above expression can be broken down as 1) Allocate raw memory 2) Initialize the Singleton object by calling its constructor 3) Assign the address of the instance to mInstanceP member variable.
These instructions in order to give the desired result should be executed in an acceptable order, but its totally upto the compilers to determine the execution order or optimize to merge the instructions to execute. This optimization might fail in certain scenarios like parallel processing of multiple instructions. Since the language does not provide any constructs to mandate the constraints for instruction execution order, we have little in our hands we can do.
We can intersperse the execution statements with temporaries but then again the compilers are smart enough to phase them out during dependency analysis.

Another scenario where DCLP fails is their portability to hardware platforms that have nonatomic pointer or integral assignment semantics. Such scenarios are possible on systems where memory addresses cross alignment boundaries, thereby requiring two fetches from memory for each access. In this case, it may be necessary to use a separate wordaligned integral flag instead of using a non aligned instance pointerthe instance pointer.

Yet another problem arises if an aggressive optimizer caches the conditional variable say in a register and does away with the second conditional check. This problem can however be resolved using the volatile keyword at the cost of some lost optimization.

References

[1] DCL Pattern - Schmit & Harrison
[2] Dev C++ Article - Why DCLP isn't 100% thread safe
[3] C++ and The Perils of Double-Checked Locking: Part I
[4] C++ and The Perils of Double-Checked Locking: Part II

What people said... (2)

Rajan Karol said...

Volatile modification

Rajan Karol said...

This is a sample comment to test the post's commenting functionality. Put in your comments.