My GitHub page, containing various open-source libraries for Mac and iOS development, and some miscellaneous projects

Deadlocks and Lock Ordering: a Vignette

Every once in a while, when writing threaded code, you may find yourself wanting to acquire two different locks in a critical section. Normally one should resist such perversions, but sometimes they just end up being necessary, or too tempting. Holding multiple locks at the same time immediately raises the specter of deadlock: if two threads acquire the same locks in a different order, they can end up waiting on each other forever.

The solution to this conundrum is to enforce a lock order. Sometimes there's a natural ordering, like parent and child. But sometimes you just have two locks and you need to take both. Code like this can be dangerous:

@synchronized ( a ) { @synchronized ( b ) { // do stuff } }

If two threads have opposite ideas of what a and b are, this carries the potential for deadlock. Some way needs to be found to ensure that every thread locks the same object first. In the absence of any other way to do it, an ordering can be imposed on these objects by comparing their addresses. If we always lock the one with the lower address first, we'll never deadlock, even with multiple threads grabbing the objects from different sources and not communicating with each other.

Fortunately, this comparison is easy, since C allows using the comparison operators on pointers of the same type:

id min , max ; if ( a < b ) min = a , max = b ; else min = b , max = a ; @synchronized ( min ) { @synchronized ( max ) { // do stuff, safely } }

This eliminates the risk of deadlock. Hooray!

You might notice that the code for finding the object with the lower address looks sort of like the code you might write for finding the smaller integer from two different int varibables. Sort of exactly like the equivalent code for integers, in fact. The only difference is the types.

Thus the punchline: Cocoa's MIN and MAX variables, being written in a completely type-generic fashion, work on object pointers just as well as they do integers and floats. They can be used to solve this problem a little more nicely:

@synchronized ( MIN ( a , b )) { @synchronized ( MAX ( a , b )) { // do stuff, safely } }

This code is equivalent to the above, but much more compact without sacrificing any readability or safety. Except for the part where you use MIN and MAX on pointers, you might consider that to sacrifice readability. Depends on personal taste.

This works with other locking constructs as well. For example:

NSLock * aLock = [[ NSLock alloc ] init ]; NSLock * bLock = [[ NSLock alloc ] init ]; ... [ MIN ( aLock , bLock ) lock ]; [ MAX ( aLock , bLock ) lock ]; // do stuff, safely [ MAX ( aLock , bLock ) unlock ]; [ MIN ( aLock , bLock ) unlock ];

It's a little crazy to see a MIN macro as the target of an Objective-C message send expression, but it works just fine. Even pthread mutexes and spinlocks can use this, as long as you apply it to their pointers, and not to the values themselves.

If you ever find yourself needing to acquire two locks at once, first, think very hard to see if you can avoid doing so. But if you must, the MIN and MAX macros make for an easy way to ensure a consistent, deadlock-free lock ordering across all of your various threads.

Did you enjoy this article? I'm selling whole books full of them! Volumes II and III are now out! They're available as ePub, PDF, print, and on iBooks and Kindle. Click here for more information

Comments:

Add your thoughts, post a comment:

Spam and off-topic posts will be deleted without notice. Culprits may be publicly humiliated at my sole discretion.

JavaScript is required to submit comments due to anti-spam measures. Please enable JavaScript and reload the page.