The Problem: Anatomy of a Deadlock
Your program contains a potential deadlock if:
- One part of your program tries to acquire exclusive use of two shared resources (such as mutexes)
a
andb
at the same time by acquiring firsta
and thenb.
- Some other part of your program tries to do the same by acquiring
b
and thena
in the reverse order. - The two pieces of code could ever execute concurrently.
For convenience, from now on I'm going to talk about just locks, but the issues and techniques apply to any shared resource that needs to be used exclusively by one piece of code at a time. The following code shows the simplest example of a potential deadlock:
// Thread 1: a then b // Thread 2: b then a a.lock(); b.lock(); b.lock(); a.lock(); ... ... // unlock a and b // unlock a and b // in either order // in either order
The only way to eliminate such a potential deadlock is to make sure that all mutexes ever held at the same time are acquired in a consistent order. But how can we ensure this in a way that will be both usable and correct? For example, we could try to figure out which groups of mutexes might ever be held at the same time, and then try to define pairwise ordering rules that cover each possible combination. But that approach by itself is prone to accidentally missing unexpected combinations of locks; and even if we did it perfectly, the result would still be at best "DAG spaghetti"a directed acyclic graph (DAG) that nobody could comprehend as a whole. And every time we want to add a new mutex to the system, we would have to find a way fit it into the DAG without creating any cycles.
We can do better by directly exploiting the knowledge we already have about the structure of the program to regularize the mess and make it understandable.