The design decisions you make affect not only how well you can maintain your code but also how well others can use your code. Multithreading adds a new dimension to your designs and I’ll give you some of my thoughts in this episode.
The example class used in this episode is called TotalsTen and consists of three properties:
left -> an integer property value that can be read and written.
right -> an integer property value that can be read and written.
perfectScore -> a bool property value that can be read.
The idea behind this class is that the perfectScore property will be true only when the left and right values add up to the value ten.
This class sets us up for a race condition and makes it difficult to fix due to the design. I explain how to improve this design while still keeping the original purpose of the class intact. Listen to the full episode or you can also read the full transcript below.
Managing multiple threads takes a lot of thought and in order to do this effectively, you really need to know how the threads are being used. This presents a problem for developers writing code designed to be used in many different places. Many times, the code you write will be at the wrong level of abstraction to deal with threads properly. What do I mean by this?
Let’s say you have a class called TotalsTen that has two integer properties called left and right that can be read and changed independently of each other. The TotalsTen class has another property called perfectScore which will be true only when the left and right properties add up to ten.
I have no idea what possible purpose a class like this could serve except that it seems like a great example to use for this episode because it’s simple and doesn’t need any special diagrams to understand. It’s just a class with a left int and a right int that when they add up to the value ten, cause another perfectScore property to be true. Any other values that don’t add up to the value ten will cause the perfectScore property to be false. You can’t set the perfectScore property yourself. It gets its value depending on the sum of the left and right properties.
Alright, now that we have this highly made-up class, what’s it have to do with multithreading?
A class like this is a utility class. It’s designed to perform some function which contributes in some way to an application. This is code that will be used inside an application. So let me ask you. Does this application need to be multithread aware? I have no idea and it’s not possible to know all the needs of all the applications that may want to use your utility class.
If you make the class multithread safe and it’s then used in a single threaded application, then not only does your extra work add no value, it actually slows down the application.
And if you don’t make your utility class thread safe, then you’re pushing that work onto the application developer.
There is no perfect answer to this problem. But I can give you some advice to help make it better right after this message from our sponsor.
( Message from Sponsor )
First of all, this TotalsTen class as it’s currently designed is just asking for race conditions. Check out episode 94 for more information about race conditions. Let’s say that the current values of left and right are 1 and 9. They add up to 10 so the perfectScore property is true. And let’s say there’s a thread that wants to change the values to 3 and 7. Right after this other thread changes left to be 3, your thread comes along and reads the values 3 and 9 which means the perfectScore is false. A moment later, the other thread finishes changing the right value from 9 to 7 which brings the perfectScore back to true.
If we wanted to make this class thread safe, it would be difficult with the two separate property setters. You can go way back to episode 31 to learn more about getters and setters. We need a critical section to lock the update of both property values but we can’t do that with separate methods unless we ask the application code to do this for us.
That’s definitely an option. But if we’re going to do that, then we might as well just declare that this class is hopelessly thread unaware and put all the responsibility on the calling code to figure it out. I’m not saying this is a bad idea. If a lot of the applications that will use your utility class are single threaded, then this is a great option because it keeps your class simple and fast.
Even so, I’d probably still change the design so that the left and right properties are getters only just like the perfectScore is only a getter. But wait a minute, if all the properties are read only, then how can the left and right values be changed? Simple. Create a method designed to do just that. Let’s call this method updateScore and have it take two int parameters that will be assigned to the left and right properties.
The reason I like this design better is because it draws attention to the relation between the two left and right properties and how they affect the perfectScore property.
And for multithreading, this design gives us some better options too. Now that we have a single method that updates both left and right property values, we can lock access to this class when the method begins, update both values, and then release the lock.
The only other thing we need to do is add code to obtain the same lock whenever any of the properties are read. Why do we need locks for reading? Aren’t these locks only there to prevent multiple writers from interfering with each other? Episode 94 describes race conditions and explains that you can lock a room when you enter it and update a number on a whiteboard. You need to lock the room so nobody else can enter and change the number at the same time. That’s true. But you also don’t want somebody to be able to walk in and read your unfinished changes. It doesn’t matter if the other person intends to make changes or not. As long as you have a lock, you’re effectively saying “Keep out until I’m done.”
We then need to obtain the same lock as in the updateScore method in all the read only property getters for the left, right, and perfectScore properties. This ensures that if another thread tries to read any of these properties while your thread is in the process of changing them, then that other thread will have to wait.
It has a side effect though that multiple threads just trying to read properties will also block each other. There are ways to get around this with something called a reader-writer lock but that adds extra complexity. I’ll describe that in a future episode.
Alright, what have we done so far to this TotalsTen class? By rearranging the properties so that the related properties left and right are updated in a separate method, we’ve made it much easier to make this code thread safe. We still don’t know if the code needs to be thread safe though. We might be doing all this work for nothing. Well, I’d argue that moving related properties updates to their own method is a good change regardless.
One option is to let the calling code decide if it needs a thread safe TotalsTen class or not. You have quite a few options here. You could have derived classes, one that’s thread safe and one that’s not. You could have a constructor option that turns on thread synchronization code or not. Or if you’re using C++ with full support for template programming, you could use a true and false template argument with some specialized templates to handle thread synchronization or not.
Another option is to release your utility class in two completely different libraries, one that’s thread safe and one that’s not.
The main thing to understand when developing code that can be used in many places is that you at least need to be aware of multithreading and put some thought into how your code will be used. Because if you don’t, then your code may not be usable at all in many cases.