104: Multithreading. Singleton Mistakes.
Take Up Code - Un pódcast de Take Up Code: build your own computer games, apps, and robotics with podcasts and live classes
Categorías:
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. Transcript 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.