Introduction
Okay, you're not a newbie, you know all about singletons (only one object instantiation during program execution), but do you really know everything there is to know about Singletons and thread safety?
Background
Who is this article for?
- This article is for people who know about Singletons and want to learn about thread safety for Singletons.
- This article is mainly for discussing design and for C++, though you are okay knowing any other OOPS language. Do note, managed languages have additional solutions.
This article is not for people who do not know:
- What thread safety means.
- Basic understanding of Singleton – you might still be okay if you know about static methods.
- Design gurus and experts; this article is targeted for intermediate developers and designers in the making.
The Singleton
Let’s write a quick pseudo code for Singletons:
class MySingleton
{
public:
static MySingleton * GetInstance()
{
if (m_pOnlyOneInstance == NULL) {
m_pOnlyOneInstance = new MySingleton();
}
return m_pOnlyOneInstance;
}
private:
static MySingleton * m_pOnlyOneInstance;
MySingleton();
public:
void foo();
bool goo();
int zoo();
};
Thread Safety Issue for Singletons
Remember, thread-safety issue for Singletons would occur only rarely, as follows (rarely, but still catastrophic! so you still need to design for it):
- No client code has called
GetInstance()
so far, and now two threads simultaneously call GetInstance()
, and - Context switch between the two calling threads happen on the exact line of code at:
if (m_pOnlyOneInstance == NULL)
During further calls to GetInstance()
, the MySingleton
object is already created and would be returned. But it's still a serious issue, as we've instantiated MySingleton
twice.
Have you made your Singleton Thread-Safe?
Note: Think about how you'll make your Singleton thread-safe, come up with solutions before proceeding further.
Solution 1
Easy, put a critical section to my Singleton method to make it thread-safe. Duh, is it gonna be that kinda article?
MySingleton * GetInstance() {
EnterCriticalSection();
if (m_pOnlyOneInstance == NULL)
{
m_pOnlyOneInstance = new MySingleton();
}
LeaveCriticalSection();
return m_pOnlyOneInstance;
}
Deep Dive into Solution 1
Yup, this solution works, but think about it: critical section is a costly operation, and you're using it each and every time a client accesses GetInstance()
.
You've devised a solution that works and handles the rare but serious thread safety issue for singletons, but at the cost of doing an expensive critical section operation for all GetInstance()
calls, slowing down client access every time!!
This is clearly unacceptable. Isn't there a better solution?
Solution 2
Okay hot shot, if critical section is expensive, let's give it the boot.
Let's call MySingleton::GetInstance()
during program start-up, like in main()
in C++, or CWinApp:InitInstance()
in MFC, and so on an so forth.
I know there is only one thread executing during program start-up, so thread-safety issue does not even arise.
Design Principle: This kind of instantiation is called Eager Instantiation. That means, creating objects up-front, even before they are required or might be used.
Yup, this works. No critical section involved, so no costly operation for the general use-case when clients call GetInstance()
every time.
Wait a minute, trying to pull a fast one, eh? I know about the basic OOAD Design Principle of Lazy\Late Instantiation, which means create an object only when required, not upfront. Aren't we breaking this design principle?
Heck, yes, we are! Let's plunge into this. But let's quickly define Late Instantiation before that:
Design Principle: Late Instantiation means creating an object when it is required to be used, not up-front.
We’ve covered two design principles of Early and Late Instantiation already!
Deep Dive into Solution 2 (keep your beach towels ready!)
- What if no client calls
MyInstance()
during program execution? Maybe the client ran a use-case this time that did not need MySingleton
's usage. You've created an unnecessary object that's floating around during the entire program life-cycle doing nothing. - While Early or Lazy Instantiation might not sound like a big deal, what if
MySingleton
is a memory-hogging class? What if MySingleton
represents data stored on a file, or detailed info about a server? You're occupying lot of precious memory that might never potentially be used! - Eager Instantiation is not all bad. If your Singleton is a basic class that is heavily used all across your program, then by all means, go for Eager Instantiation.
Lazy Instantiation is a principle, not a rule, and not necessarily always the default choice. Be either Eager or Lazy, depending on your design and domain needs! There is no good or bad, you have to choose what's best based on your program needs. Solution 2 (Eager Instantiation) is a pretty good, easy, clean solution for many projects, and is widely used.
But isn’t there a solution to address these short-comings of Solution 2? Yup, there is! Enter Solution 3.
Solution 3
We can achieve good performance and lazy instantiation for Singletons (which were the short-comings of Solutions 1 and 2 for the short-term memory loss readers out there).
You can achieve this by moving around code in Solution 1.
Do go back to Solution 1 and think about how this can be done before proceeding further.
Deep Dive into Solution 3
MySingleton * GetInstance()
{
if (m_pOnlyOneInstance == NULL)
{
EnterCriticalSection();
if (m_pOnlyOneInstance == NULL)
{
m_pOnlyOneInstance = new MySingleton();
}
LeaveCriticalSection();
}
return m_pOnlyOneInstance;
}
With Solution 3, you do not use a critical section every time a client calls GetInstance()
, and we achieve Lazy Instantiation. The MySingleton
object is created only when the client calls GetInstance()
.
Also, a Critical Section is used only during instantiation, and for handling the rare (but catastrophic!) thread-safety issue during instantiation and the race condition between two threads. We do not enter a critical section block every time the client calls GetInstance()
.
Congratulations! You’ve just learnt Double-Checked Locking, the formal name for Solution 3.
Summary
We’ve covered quite a bit of ground there with the simplest of Design Patterns: the Singleton. We’ve applied a mini-OO Analysis and Design on a small scale for our friend MySingleton
. And, as a bonus, we learnt about Eager and Lazy Instantiation!
Remember, you can choose either Solution 2 or Solution 3 for your Singletons, based on your project need.
There is another pattern called Monostate with which you could achieve Singleton-like behavior, but it won’t please the purists out there.
The Monostate pattern is simply making all methods and data of your class static
.
class MonoState
{
public:
static void foo();
static bool goo();
static int zoo();
MonoState() {}
private:
static int MyData;
};
Clients of MonoState
access it by creating an object and calling the static method. Since all methods and variables are static
, data is shared across objects and we get a Singleton like behavior.
MonoState newObject;
newObject.goo();
MonoState msObject;
msObject.zoo();
But note that what we get from MonoState is Singleton like behavior, because MonoState creates multiple objects but that which share the same data. Anyway, MonoState is theoretical, and for the sake of rounding up a complete article, you should always be going for the Singleton pattern, rather than MonoState.