UNIVERSITY OF WISCONSIN-MADISON
Computer Sciences Department
CS 537
Spring 2001
A. Arpaci-Dusseau
Quiz #4: March 7th -- Dining Philosophers
Name: Solutions Student ID #: Solutions

Problem 1: Basic Semaphores

A classic synchronization problem, presented by Dijkstra in 1965, is the Dining Philosophers Problem. In this problem, there are 5 philosophers sitting at a round table. Philosophers repeat (forever) thinking and eating. There is a single chopstick shared between each pair of philosophers. As you know, a philosopher must have two (2) chopsticks to be able to eat!

Below is code that attempts to solve the Dining Philosophers Problem with a single semaphore for each chopstick. Assume that Think() and Eat() are methods provided to you (already written), but you don't know how long they take to complete; also assume the Semaphore object is implemented correctly.

Unfortunately, this code makes a timing assumption about how it is started in main(), can deadlock, and does not initialize the semaphores. To fix these problems, you need to do three things:

  1. First, describe the problem that could occur given the current order in which Philosopher objects are created and threads are started in main(). Second, fix the code in main() such that it always works correctly, regardless of the behavior of the scheduler.
  2. Without adding any state variables, fix the Philosopher code so that it cannot deadlock. You do not need to make the code as efficient as possible. Make as few changes as you can.
  3. Specify the correct initial values for the semaphores.

public static void main(String[] argv) {

    Philosopher p[] = new Philosopher[5];
    Thread t[] = new Thread[5];

    for (int i = 0; i < 5; i++) {
        try {
            p[i] = new Philosopher(i);
            t[i] = new Thread(p[i]);
            t[i].start();
        } 
        catch (Exception e) {
            System.out.println("ERROR!\n");
            System.exit(1);
        }
    }
}

class Philosopher implements Runnable{

    private static Semaphore chopstick[5];
    private int p;

    Philosopher (int id) {
        p = id; 
        chopstick[id] = new Semaphore(??);       
    }

    private void take_chopsticks() {
        chopstick[(p + 1) % 5].P();
        chopstick[p].P();
    }

    private void put_chopsticks() {
        chopstick[p].V();
        chopstick[(p + 1) % 5].V();
    }

    public void run() {
        while (1) {
            Think();
            take_chopsticks();
            Eat();
            put_chopsticks();
        }
    }
}

1. The startup code will not work correctly if the scheduler decides to start up and schedule a philosopher thread before the semaphore associated with another philosopher has been created and initialized.

The following code works correctly no matter how scheduler decides to run the philosopher threads:

for (i = 0; i < philCount; i++) {
    p[i] = new Philosopher(i);
    t[i] = new Thread(p[i]);
}    

for (i = 0; i < philCount; i++) {
    t[i].start();
}    

2. The following code fixes the deadlock problem by removing the circular nature of requests for grabbing chopsticks.

    private void take_chopsticks() {
            if (p!=0) {
                fork[p].P();
                fork[(p + 1) % 5].P();
            } else {
                chopstick[(p + 1) % 5].P();
                chopstick[p].P();
            }
    }

3. The semaphores should be initialized to 1.

chopstick[id] = new Semaphore(1);       

Problem 2: Semaphores with State Variables

Below is code that solves the Dining Philosopher problem such that it is live as well as safe; this is done with the addition of a state variable for each philosopher, designating whether they are THINKING, EATING, or HUNGRY. You may assume that the run() method is the same as in the code above and that all of the Philosophers are created, initialized, and started correctly. This is the same code presented in class and works correctly (assuming it is initialized appropriately).

class Philosopher implements Runnable{

    private static Semaphore mayEat[5]; // How is this initialized?
    private static Semaphore mutex;     // How is this initialized?
    private static int state[5];        // Initialized to THINKING
    private int p;                      // Initialized to a unique id

    private void take_chopsticks() {
         mutex.P();
         state[p] = HUNGRY;
         test(p);
         mutex.V();
         mayEat[p].P();
    }
    private void put_chopsticks() {
        mutex.P();
        state[p] = THINKING;
        test(p+1%5);
        test(p+4%5);
        mutex.V();
    }
    private void test(int i) {
        if (state[i]==HUNGRY && state[i+1%5]!=EATING && state[i+4%5]!=EATING) {
            state[i] = EATING;
            mayEat[i].V();
        }	
    }
}

How should the Semaphore elements of mayEat be initialized?

Initialize to 0, so that V() operation must occur for P() to complete.

How should the Semaphore mutex be initialized?

Initialize to 1, so that exactly one process can be in critical section at a time.

What is the maximum number of Philosophers that can be waiting on a Semaphore element mayEat[i] at any given time?

One. Each philosopher i waits only on its own mayEat[i] variable.

What is the maximum number of Philosophers that can be waiting on mutex at any given time?

Four. Given that there are five philosophers, if one is in the critical section, the other four can be waiting to acquire the mutex.

Does the code work correctly if mutex is not declared a static variable? Briefly describe why or why not.

No, this will not work. If mutex is not static, then each Philosopher has its own mutex variabile and there is no mutual exclusion across Philosopher threads in the get_chopsticks() and put_chopsticks() code.

Does the code work correctly if the statement mayEat[i].P is moved before mutex.V() in take_chopsticks()? Briefly describe why or why not.

No, this will not work. If a philosopher must wait on the mayEat[i].P() operation and does not release the mutex (i.e., call mutex.V()), then no other philosophers will be able to wake the philosopher by calling mayEat[i].V() since this operation is inside of the critical section as well.

Does the code work correctly if the statement mayEat[i].V is moved before state[i]=EATING() in test()? Briefly describe why or why not.

Yes, the code does work correctly. Since these operations are performed when the mutex is held, the scheduling of other Philosophers can't be interleaved, and so they can't tell what order the mayEat[i].V versus the state[i]=EATING() were executed.

Does the code work correctly if the statements test(i+1%5) and test(i+4%5) are moved before state[i]=THINKING() in put_chopsticks()? Briefly describe why or why not.

No, this will not work. The philosopher must change its state from EATING to THINKING before testing whether one of its neighbors is now ready to eat; if it still looks like this philosopher is EATING, the neighbors will not be able to eat.