next up previous contents
Next: Programming primitives for mutual Up: Concurrent Programming Previous: Race conditions   Contents

Protocols based on shared variables

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.


next up previous contents
Next: Programming primitives for mutual Up: Concurrent Programming Previous: Race conditions   Contents
Madhavan Mukund 2004-04-29