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 runsCaution 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.waitBut 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.