This question was asked in an interview. The first part was to write the singleton class:
class Singleton
{
static Singleton *singletonInstance;
Singleton() {}
public:
static Singleton* getSingletonInstance()
{
if(singletonInstance == null)
{
singletonInstance = new Singleton();
}
return singletonInstance;
}
};
Then I was asked how to handle this getSingletonInstance()
in a multithreaded situation. I wasn't really sure, but I modified as:
class Singleton
{
static Singleton *singletonInstance;
Singleton() {}
static mutex m_;
public:
static Singleton* getSingletonInstance()
{
m_pend();
if(singletonInstance == null)
{
singletonInstance = new Singleton();
}
return singletonInstance;
}
static void releaseSingleton()
{
m_post();
}
};
Then I was told that although a mutex is required, pending and posting a mutex is not efficient as it takes time. And there is a better way to handle to this situation.
Does anybody know a better and more efficient way to handle the singleton class in a multithreaded situation?
In C++11, the following is guaranteed to perform thread-safe initialisation:
static Singleton* getSingletonInstance()
{
static Singleton instance;
return &instance;
}
In C++03, a common approach was to use double-checked locking; checking a flag (or the pointer itself) to see if the object might be uninitialised, and only locking the mutex if it might be. This requires some kind of non-standard way of atomically reading the pointer (or an associated boolean flag); many implementations incorrectly use a plain pointer or bool
, with no guarantee that changes on one processor are visible on others. The code might look something like this, although I've almost certainly got something wrong:
static Singleton* getSingletonInstance()
{
if (!atomic_read(singletonInstance)) {
mutex_lock lock(mutex);
if (!atomic_read(singletonInstance)) {
atomic_write(singletonInstance, new Singleton);
}
}
return singletonInstance;
}
This is quite tricky to get right, so I suggest that you don't bother. In C++11, you could use standard atomic and mutex types, if for some reason you want to keep the dynamic allocation of you example.
Note that I'm only talking about synchronised initialisation, not synchronised access to the object (which your version provides by locking the mutex in the accessor, and releasing it later via a separate function). If you need the lock to safely access the object itself, then you obviously can't avoid locking on every access.