There’s a big problem with Singletons especially in the C++ language. It’s not obvious how to get them to work with multiple threads. You want one instance in your entire application and how do you handle the race condition when multiple threads ask for the instance at the same time?
Or maybe I should say that while the solution is obvious, it’s often then modified into something that no longer works correctly. There’s a desire to avoid obtaining a lock when we know it’s no longer needed. It probably wouldn’t be so bad if the lock really was needed throughout the application’s lifetime. But because of the way Singletons work, they really only have the potential for a race condition early in the application lifetime. It seems like a waste to continue getting a lock when it’s not needed anymore.
A common “solution” tho this is to use the Double-Checked Locking Design Pattern. This adds very little extra complexity and seems to work perfectly. the only reason it seems to work though is because of the slim chance of hitting the race condition in the first place. It doesn’t actually solve the problem. All it really does is avoid the need to obtain a lock once the initial possible race condition is over.
There are still problems, especially in C++. This episode explains how just checking the instance variable twice doesn’t really solve the problem. Listen to the full episode or you can also read the full transcript below.
Episode 60 describes the Singleton pattern and why you might want to use it and how. This episode dives deeper into the implementation of this simple design pattern and explains some problems you’ll encounter when making it thread safe.
While I can’t easily read code to you over the podcast, this pattern is simple enough that it usually fits in a handful of lines. I’ll describe what each line does and leave out all the semicolons and other minute details.
Alright, you know that you need a static method called instance that returns a pointer to the single instance of your singleton object. The instance method needs to figure out if the singleton has already been created yet or not, right? That means the method will start out with an if statement checking if the internal static pointer is null or not. A null pointer means that the singleton hasn’t been created yet. And a valid pointer means that the singleton has already been created and that’s the value that should be returned to the caller.
If the static pointer is null, then the code goes into a one line statement that constructs a new instance and assigns the instance to the static pointer. Then the newly constructed object can be returned to the caller.
All total, we have an if statement that checks for null, a construction and assignment statement, and a return statement. Just three lines of code and you have a Singleton pattern that’s completely broken with multiple threads.
So how do you fix it?
Like any race condition, you need a lock, right? Okay, you go ahead and add a lock right away when the instance method begins. This actually works and works reliably. The problem is that most developers then try to make it better.
You see, the first thing that quickly becomes annoying is the need to obtain that lock every single time the singleton is needed. Once the initial race condition is over, for the rest of the application, the lock isn’t needed anymore. Yet there it is slowing down the program.
At some point, somebody came up with what appears to be a great idea and it even has a name. It’s called the double-checked locking pattern. At least it would be a pattern if it actually worked correctly. And you probably could get it working correctly in C# or Java with some clever use of volatile variables or some explicit memory barriers. Make sure to listen to the previous episode about volatile to learn more.
Here’s how it works. Or almost works I should say. You start out with the initial check if the instance pointer is null or not. If it’s not null, then you have a valid singleton instance and you must be past the initial race condition. So you just return what you have. And you avoided the lock completely.
The only time you need the lock is when the instance variable is still null. This means you’re in that beginning phase and there could be other threads in a race to get the first singleton instance. So you try to get the lock before going any further. Being the clever developer that you are, you recognize that multiple threads could have made the initial check and found a null pointer and all tried to get the lock at the same time. Only one of the threads will make it through first. But you can’t tell just from obtaining the lock if your thread was the first one through or not. So you have to check the value of the instance variable again inside the lock.
This is actually what you would have had to do anyway. With any locking situation, you should first get the required locks and then begin your work. Don’t make any decisions about what you think needs to be done until you have your locks.
Once you’re inside the lock, you can make that second check for a null instance pointer and if needed, then you go ahead and construct the first singleton instance and assign the static instance pointer so other threads and other future calls won’t have to go through all this again.
If you make it into the lock and discover that the instance has already been set, then you’re done. The lock served its job and allowed you to avoid a race condition corruption.
It all seems to be working just fine, right? Not really. I’ll explain how even with the double-checked locking pattern in place, things can still go horribly wrong right after this message from our sponsor.
( Message from Sponsor )
The real problem with the double-checked locking pattern is that it allows access to some critical decision information from outside of the lock. The fact that for 99% of the lifetime of your application, the lock isn’t needed anymore doesn’t make that initial race condition correct. Here’s how it can still go wrong.
Remember that compilers are allowed to reorganize statements. We have what looks like a single statement inside the if condition that checks for a null instance pointer. this single statement constructs a new instance of the singleton object and assigns it to the static pointer variable. But lurking deep inside that assignment and constructor are actually multiple events.
Whenever you construct a new instance of an object, you need to do two things. You first need some memory. If the object is a local method variable on the stack, then you have your memory already. But if not, then you need to get some from the main heap memory. The Singleton pattern can’t use a stack variable instance because we want the instance to remain long after the instance method returns.
Once you have your memory, you need to construct the object. This involves calling the constructor method. To be clear, all you did in the code was write “new object” where object is the type of whatever you’re creating. This performs the double action of getting memory and calling the constructor for you.
Once we have the memory and have called the constructor, only then do we have a valid instance. The Singleton pattern then needs to remember this instance by saving it to the static instance pointer variable.
What looked like a single statement in code results in these three actions. and as long as all three actions have taken place before your next line of code begins, then the compiler has done its job.
And that’s where we can run into problems. Let’s say that the compiler decides to grab some memory and assign the address of this memory to the instance variable before calling the constructor. It can do that without breaking any of it’s rules. And because we’re inside a lock and have exclusive access to everything, then nothing can go wrong. Except it does.
Because some other thread can come along at the worst possible time and look at the instance pointer from outside of the lock. And it could find the pointer already pointing to some valid memory. It’ll think that it’s past the initial race condition and go ahead and use the singleton object. But what it’s using is some newly allocated memory that hasn’t yet had time to complete the construction process. It’s like rubbing up against fresh paint with your best clothes. Not a good situation.
So how do we fix this? We can’t really. At least not with the double-checked locking pattern. The only absolute safe approach is to get that lock first thing before making any decisions. Had the other thread done this, then it would have waited until the whole construction had a chance to complete and the first thread exited the lock. But in an attempt to avoid getting a lock, we exposed the potential for some thread to gain access to the partial work of another thread.
This situation can apply anywhere. For some reason, though it seems like a common problem with Singletons. I think it just has to do with the desire to avoid locking the entire life of the application when it’s only needed in the beginning.
You have some options. First of all, you could avoid the idea of trying to lazy construct the Singleton on demand. Just go ahead and construct one at the beginning of your application. If you know you’re going to need it and it doesn’t cause too much extra delay to the startup time of your application, then this might be a good approach.
Or another approach is to make sure to request the singleton instance a little later in your application but still early enough that you haven’t started creating any extra threads yet. Remember that all multithreaded applications start out with just a single thread. If you go through the simple singleton steps when you only have one thread to worry about, then you’ll be okay.