In the preceding subsection, we showed how object locking can be used to prevent interference. Unfortunately, locking is not a panacea. The excessive use of locking can severely degrade system performance or worse lock up the system so that all computational progress halts until the program is terminated. The essence of concurrent programming is organizing computations so that neither interference or deadlock can occur.
To illustrate deadlock, let us consider a classical problem. It is unrealistic and fanciful, but the synchronization behavior that it models can happen in real systems.
A collection of N philosophers sits at a round table, where N > 1. N forks are placed on the table, one between each pair of adjacent philosophers. No philosopher can eat unless he has two forks. Obviously, adjacent philosophers cannot eat at the same time. Each philosopher alternately eats and sleeps, waiting when necessary for the requisite forks before eating.
Our task is to write code simulating the dining philosophers so that no philosopher starves. An obvious protocol that each philosopher might follow is:
while (true) { grab left fork; grab right fork; eat; release left fork; release right fork sleep; }
Now assume that actions of the philosophers are perfectly interleaved: the first philosopher grabs his left fork, then the second philosopher grabs his left fork, and so on until the Nth philosopher grabs his left fork. Then the first philosopher tries to grab his right fork, the second philosopher tries to grab his right fork, and so on. They all have to wait because no right fork is available and they all starve.
Theoretical computer scientists have proven that there is no deterministic uniform solution to this problem. (By uniform, we mean that every philosopher executes exactly the same code with no access to identifying state information such as the name of the ``current'' philosopher.) But many non-uniform solutions exist. For example, we could number the philosophers. Even numbered philosophers ask for the left fork first, odd numbered ones ask for the right fork first.
Another common solution to this sort of deadlock is to order the resources (in this case forks) and force the processes (philosophers) to grab forks in ascending order. This solution is very general and is widely used in practice.
Consider the case where we have 3 philosophers: P1, P2, P3. Then we order the forks F1, F2, F3.
Now, no matter what, all the philosophers will be able to eat. For instance, if P0 gets F0, and P1 gets F1, P2 must wait until F0 is free. So P1 will get F2 (since there will be no contention), and finish eating. This will release F1 and F2, allowing P0 to get F1 and finish eating. Finally, this will release F0, allowing P2 to get F0 and F2 (since there will be no further contention) and finish eating.