Return to the Lecture Notes Index

Lecture 8 (February 2, 2000)

Many thanks to Chris Palmer for his contribution to today's notes. -GMK

Reading

Chapter 7

A Few More Words About Monitors

Three Different Types of Monitors

If P does X.wait and Q does X.signal:

Hoare Monitors - Q blocks and P runs
Mesa Monitors - Q continues, P runs eventually (after Q) BH Monitors - Q can only signal as it exits, P runs

Caution with Mesa Monitors

Mesa monitors do not ensure that the conditions that were true when a process or thread is signaled by another remain true at the time that it runs.

To shield ourselves from this situation, we might need to change our entry test from an "if" statement to a "while" statement:

if (something) X.wait --> while (something) X.wait

But this allows for starvation. It could be the case that each time a previously blocked process gets to run, the conditions have changed and it is forced to block again.

Consider the example below:

  MONITOR prod-con
  {
    struct something buffer[n];
    int in=0, out=0;
    int count=0;
    condition full, empty;

    entry add_item (data)
    {
      if/while (count == n) full.wait;

      buffer[in] = data;
      in = (in + 1 ) % n;
      count++;
  
      empty.signal;
    }


    entry remove_item (data_ptr)
    {
      if/while (count == n) empty.wait;

      *data_ptr = buffer[out];
      out = (out + 1) % n;
      count--;

      full.signal;
    }

  }
  

This example is correct for Hoare monitors using the if, but correctness requires that this become a while under Mesa semantics. This change introduces the possibility for starvation.

Student Question: Why would we ever want Mesa monitors?

Answer: They are more efficient, sicne we can just move the signaled thread from the blocked to the ready queue. And since they involve fewer context switches, they are often more efficient.

Dining Philosophers Problem

The Dining Philosophers Problem is a classic example in computer science. Consider a round table populated by five philosophers. Each philosopher has a plate. In-between each plate is a fork. At the center of the table is a bowl of spaghetti.

The rules of the problem are these:

Note: With 5 people, it is impossible for three philosophers to eat concurrently, because there are only 5 forks (2.5 pairs).

Semaphore-based Solution


  #define left(i) (i)
  #define right(i) ((i-1) % 5)

  semaphore fork[5] = {1, 1, 1, 1, 1};
  semaphore table = 4;

  while (1)
  {
    << think >> 

    P(table);
    P(fork[left(i)]);
    P(fork[right(i)]);

    << eat >>

    V(fork[right(i)]);
    V(fork[left(i)]);
    V(table);
  }

But this solution has a problem: deadlock. If everyone picks up the left fork before anyone picks up the right fork, deadlock occurs. So how do we fix this problem?

Solution #1: If we require that the philosophers always pick up their left fork before picking up their right fork, deadlock is avoided.

Broken Suggestion: Pick up a random fork first. Well, the random forks could all be the left fork, so deadlock is still possible.

Broken Suggestion: If you can't pick up the right fork, put down the left fork and try again. Well, this can lead to livelock, where each philosopher picks up their left fork, looks for the right fork, puts down the left fork, and repeats.

Solution #2: Make certain thjat only 4 philosophers are trying to eat at any time. This ensures that at least one philosopher will be able to eat.

Solution #2 with Monitors

We can solve the deadlock and livelock problems and implement Solution #2 using a monitor. But this solution allows for startvation.

MONITOR fork
{
  int avail[5] = {2, 2, 2, 2, 2}; // # of forks available to each philosopher
  condition ready[5];

  entry pickup_forks (i)
  {
    if (avail[i]) != 2) read[i].wait;
    avail[left(i)]--;
    avail[right(i)]--;
  }

  entry putdown_forks(i)
  {
    avail[left(i)]++;
    avail[right(i)]++;
    if (avail[left(i)] == 2) 
      ready[left(i)].signal;
    if (avail[right(i)] == 2) 
      ready[right(i)].signal;
  }
}

Another approach using a monitor follows. It also suffers from starvation, but it is interesting, becuase it shows the relationship between semaphores and monitors.

MONITOR dining_P
{
  int state[5] = {thinking, thinking, thinking, thinking, thinking};
  condition phil_cond[5];

  entry pickup_forks (i)
  {
    state[i] = hungry;
    test_forks(i);
    if (state[i] != eating)
      phil_cond[i].wait;
  }

  entry putdown_forks(i)
  {
    state[i] = thinking;
    test_forks(LEFT);
    test_forks(RIGHT);
  }

  test_forks (i)
  {
    if ( (state[LEFT] != eating) && 
         (state[RIGHT] != eating) && 
         (state[i] == hungry)
    {
      state[i] = eating;
      phil_cond[i].signal;
    }
  }
}

For comparison, let's look at a very similar solution using semaphores:

semaphore mutex = 1;
semaphore phil_sem[5] = {0, 0, 0, 0, 0};

pickup_fork(i)
{
  P(mutex);
  state[i] = hungry;
  test_forks(i);
  V(mutex);
  P(phil_sem[i]);
}

putdown_forks(i)
{
  P(mutex);
  state[i] = thinking;
  test_fork (LEFT);
  test_fork(RIGHT);
  V(mutex);
}

test_forks (i)
{
    if ( (state[LEFT] != eating) && 
         (state[RIGHT] != eating) && 
         (state[i] == hungry)
    {
      state[i] = eating;
      V(phil_sem[i]);
    }
}

Why Do We Keep Getting Starvation?

We keep getting starvation, because each philosopher is waiting for a different condition, so we can't ensure fairness. There are two fixes: make all philosophers wait for a common condition, or maintain an explicit queue of waiting philosophers to force fairness.

Deadlock

UNIX lets things deadlock. Users will realize that something has gone wrong and explicitly kill a process. Hopefully the process that is killed will break the dependency chain and allow the other processes to continue. If not, the user likely will kill another process, until the problem goes away.

Examples of deadlocks include the now familiar case of "holding one fork while waiting for another", performing a P() on a semaphore and forgetting to perform a V(), and traffic "grid lock."

A Model To Talk About Deadlock

We need a model to help us speak about deadlock.