One way to solve the mutual exclusion problem is to develop a protocol using auxiliary shared variables. For instance, we could use a variable turn that takes values 1 and 2 to indicate whose turn it is to access the critical region, as follows.
Thread 1 Thread 2 ... ... while (turn != 1){ while (turn != 2){ // "Busy" wait // "Busy" wait } } // Enter critical section // Enter critical section ... ... // Leave critical section // Leave critical section turn = 2; turn = 1; ... ...
Let us assume that turn is initialized to either 1 or 2 (arbitrarily) and there is no other statement that updates the value turn other than the two assignments shown above. It is then clear that both Thread 1 and Thread 2 cannot simultaneously enter their critical sections--if Thread 1 has entered its critical section, the value of turn is 1 and hence Thread 2 is blocked until Thread 1 exits and sets turn to 2. A symmetric argument holds if Thread 1 tries to enter the critical section while Thread 2 is already in its critical section. If both threads simultaneously try to access the critical section, exactly one will succeed, based on the current value of turn.
Notice that this solution does not depend on any atomicity assumptions regarding assigning a value to turn or testing the current value of turn.
However, there is one serious flaw with this solution. When Thread 1 executes its critical section it sets turn to 2. The only way for Thread 1 to be able to reenter the critical section is for Thread 2 to reset turn to 1. Thus, if only one thread is active, it cannot enter the critical section more than once. In this case, the thread is said to starve.
Another solution is to maintain two boolean variables,
request_1
and request_2
, indicating that the
corresponding thread wants to enter its critical section.
Thread 1 Thread 2 ... ... request_1 = true; request_2 = true; while (request_2){ while (request_1) // "Busy" wait // "Busy" wait } } // Enter critical section // Enter critical section ... ... // Leave critical section // Leave critical section request_1 = false; request_2 = false; ... ...
Here we assume that request_1
and request_2
are
initialized to false and, as before, these variables are not
modified in any other portion of the code. Once again, it is easy to
argue that both threads cannot simultaneously be in their critical
sections--when Thread 1 is executing its critical section, for
instance, request_1
is true and hence Thread 2 is blocked.
Also, if only one thread is alive, it is still possible for that
thread to repeatedly enter and leave its critical section since the
request flag for the other thread will be permanently false. However,
if both threads simultaneously try to access the critical region, they
will both set their request flags to true and there will be a deadlock
where each thread is waiting for the other thread's flag to be reset
to false.
Peterson [1981] found a clever way to combine these two ideas into a
solution that is free from starvation and deadlock. Peterson's
solution involves both the integer variable turn (which takes
values 1 or 2) and the boolean variables request_1
and
request_2
.
Thread 1 Thread 2 ... ... request_1 = true; request_2 = true; turn = 2; turn = 1; while (request_2 && while (request_1 && turn != 1){ turn != 2){ // "Busy" wait // "Busy" wait } } // Enter critical section // Enter critical section ... ... // Leave critical section // Leave critical section request_1 = false; request_2 = false; ... ...
If both threads try to access the critical section simultaneously, the
variable turn determines which process goes through. If only
one thread is alive, the request variable of the other thread is stuck
at false and the value of turn is irrelevant. To check that
mutual exclusion is guaranteed, suppose Thread 1 is already in its
critical section. If Thread 2 then tries to enter, it first sets
turn to 1. While Thread 1 is active, request_1
is true.
Thus, Thread 2 has to wait until Thread 1 finishes and turns off
request_1
. A symmetric argument holds when Thread 2 is in the
critical region and Thread 1 tries to enter.