I hate locks. Well, now that I have your undivided attention, I can speak the truth: I have a love and hate relationship with locks. Well, mostly hate actually.
And when I say locks, I mean Mutexes, CriticalSections, SlimLocks, ReadWriter, Monitor; all existing flavor of those.
Let’s examine them.
Pros:
- their usage is relatively easy to grasp
- almost every language offer them
- they are the ubiquitous threading tool
- every developer (is expected to) knows locks and threads
Cons:
- they only implement exclusive access to code
- they are immensely hard to master
- they do not compose
- they provide partial thread safety only
- they impair performance
Let me elaborate:
1.They only implement exclusive access to code
This sucks bad time: acquiring a lock is an explicit call to a function/method you need to insert to your code. But in fact, you rely on the lock to protect a state, i.e. data, not code. It is lock usage 101: you have to make sure that you acquire the lock every time you have to access or change the associated state.
During my debugging sessions, I have often seen this error: someone changed the state without locking the state, leading to inscrutable race conditions.
Java and C# provide the synchronized keyword. But as is this just syntactic sugar, you still face the initial issue. It would be far better if you could declare a set of field(s) as being synchronized, letting the language (or CLR/JCM) handling the heavy lifting!
2. They are immensely hard to master
I have probably held more than one hundred technical interview so far and only a couple of candidates I have met gave me the impression they were comfortable with locks and threads. Of course they are hard, it is the all point of this blog to demonstrate this.
You have to follow some arcane rules to make sure you will not encounter an issue:
- make sure you acquire the lock when you access the state (see above)
- make sure you release the lock, even in case of exceptions
- make sure you acquire locks in the proper order
- check if your lock support recursion
- take care of hidden locks (hidden in libraries, OSes)
- …
You see a pattern there? You must always be very careful when you use lock. Because no tool will help you discover if you failed to respect those rules.
3. They do not compose
Deadly sin here. The ability to build software by composing pre-existing and independent component is a must have nowadays!
But locks throw that away: if you plug some frameworks the wrong way, you risk having deadlocks, and no documentation will help you avoid this. Even worst, you can have a strong and healthy code base but see it fail badly after some framework upgrade.
You cannot put the blame on the framework authors: I have yet to see a satisfying format to document locks implementation and usage in a library.
Threads do not compose well either; most frameworks are ‘thread safe’ without offering first class support of threads (by being scalable for one). And the frameworks that expect and benefit from threads usually impose their threading model on you: they create their own thread and notify you, call your code within those.
4.They provide only partial thread safety
My concern here are frameworks and libraries that claim to be thread safe. Note that it can extended to all existing components, but it is more an issue for those two.
The catch is to understand what ‘safe’ means in thread safe. It turns out it can mean many things. Possible meanings:
- Language primitive type instances will not be corrupted in multi threaded usage. It is really the weakest safety you can provide. It basically means that no low level exception will occur due to race conditions as an example. You may be surprised, but it is exactly the level you get with System.MulticasDelegates in C#.
- Framework objects state will not be corrupted in multi threaded usage. It is the one you usually get.
- You can configure some threading attributes of the library/framework. This one is usually offered by middlewares (MOM, Databases). The accessible settings are often limited to the size of a pool of threads.
- You will not encounter deadlocks in multi threaded usage, whatever threading model you choose. This one is complex to achieve except for the most trivial libraries
- You can control the threading model for the framework. This is the one you want as a developer, but this is one you never gets.
Bottom line
Locks are the best tool most developer have to try and tame the multi threaded beast; but they still expose many pitfalls and shortcomings. A few years ago I came to the conclusion that YOU CANNOT FIX LOCKS, they will always fail, and it is impossible to brace yourself against it.
Thats where I decided there must some better alternatives, either being identified or to invent. But that will be the subject from another topic.