Friday, December 2, 2011

Be practical, how to create a thread-safe singleton?

As a programmer, each design is like making choices, this is hard. Good and experienced programmer make good choices. Here is one good example.
In many projects, you need to design a singleton, and it has to be thread safe? How to make it thread-safe?

class Foo
{
public:
   static Foo* getInstance();

private:
   static Foo* s_foo;

   Foo();
   Foo(const Foo& foo);
   Foo& operator=(const Foo& rhs);
};

Foo* Foo::s_foo = NULL;

Solution 1:  Put everything into one critical section:

Foo* Foo::getInstance()
{
    lock;
    if  (!s_foo)
       s_foo = new Foo();
   unlock;
   return s_foo;
}

Is this thread-safe?  Yes, it does both s_foo checking and construction in the critical section. But you can also easily see, it is not efficient, and no way to be used in a low-latency project.

Solution 2:  Double checked locking

Foo* Foo::getInstance()
{
    if (!s_foo)
   {
      // zone 1
       lock();
       // zone 2
       if (!s_foo)
         s_foo = new Foo();
      unlock();
   }
   return s_foo;
}

Why do we need check s_foo again inside the lock?  s_foo is NULL initially, when two threads call getInstance() simultaneously,  both will get into the zone 1, and eventually one thread gets the lock and initialized s_foo, and release the lock; then another thread goes to zone 2 also, and you can see if we don't check s_foo again here, we will create two Foo objects.

Is it efficient? Yes. But is it really thread safe? Unfortunately, no. See the following link for the out of order memory write issue associated with multi-cores.

http://www.ibm.com/developerworks/java/library/j-dcl/index.html

You can see to make it perfect, we have to tweak here and there again and again. But are all these tweaking really that necessary? Most time, it is not,

Let's see one solution:

Foo& Foo::getInstance()
{
    static Foo& f;
    return f;
}

Is this implementation perfect, of course not.  But is it practical? Yes, it is very practical, and works perfectly in almost all projects. The real beauty about it is how simple it is!. Some one might argue we can potentially create two static objects here too, it is possible. But if you initialize the static object by calling Foo::getInstance() right before it is ever used, e.g., right after parsing program arguments inside main(),  this is more than enough to make it thread safe.

No comments:

Post a Comment