Files
notes/docs/lectures/osc/07_concurrency4.md
John Gatward 1af40d53d8
Some checks failed
Build and Deploy MkDocs / deploy (push) Failing after 15s
Added osc
2026-03-25 11:15:39 +00:00

3.7 KiB

19/10/20

The Dining Philosophers Problem

img

The problem is defined as:

  • Five philosophers are sitting on a round table
  • Each one has a plate of spaghetti
  • The spaghetti is too slippery, and each philosopher needs 2 forks to be able to eat
  • When hungry, the philosopher tries to acquire the forks on his left and right.

Note that this reflects the general problem of sharing a limited set of resources (forks) between a number of processes (philosophers).

Solution 1

Forks are represented by semaphores (initialised to 1)

  • 1 if the fork is available: the philosopher can continue.
  • 0 if the fork is unavailable: the philosopher goes to sleep if trying to acquire it.

Solution: Every philosopher picks up one fork and waits for the second fork to become available (without putting the first one down).

This solution will deadlock every time.

  • The deadlock can be avoided by exponential decay. This is where a philosopher puts down their fork and waits for a random amount of time. (this is how Ethernet systems avoid data collisions)
  • Just add another fork

Solution 2

One global mutex set by a philosopher when they want to eat (only one can eat at a time)

Question: Can I initialise the value of the eating semaphore to 2 to create more parallelism?

Setting the semaphore to 2 allows the possibility of 2 philosophers to eat at one time. If these two philosophers are sitting next to each other then they will try to grab the same fork. The code will not deadlock, however only one (sometimes two) philosopher(s) is able to eat.

Solution 3

A more sophisticated solution is necessary to allow maximum parallelism

The solution uses:

  • state[N] : one state variable for every philosopher (THINKING HUNGRY and EATING)
  • phil[N] : one semaphore per philosopher (i.e. not forks initialised to 0)
    • The philosopher goes to sleep if one of their neighbours are eating
    • The neighbours wake up the philosopher if they have finished eating
  • sync : one semaphore/mutex to enforce mutual exclusion of the critical section (while updating the states of hungry thinking and eating)
  • A philosopher can only start eating if their neighbours are not eating.

img

Code for Solution 3

#define N 5
#define THINKING 1
#define HUNGRY 2
#define EATING 3

int state[N] = {THINKING, THINKING, THINKING, THINKING, THINKING};
sem_t phil[N]; // sends philosopher to sleep
sem_t sync;

void * philosopher(void * id) {
    int i = *((int *) id);
    
    while(1) {
        printf("%d is thinking\n", i);
        take_forks(i);
        printf("%d is eating\n", i);
        put_forks(i);
    }
}

void take_forks(int i) {
    sem_wait(&sync);
    state[i] = HUNGRY;
    test(i); //checks surrounding philosophers to see if its ok to eat
    sem_post(&sync);
    sem_wait(&phil[i]); //1 -> 0
}

void test(int i) {
    int left = (i + N - 1) % N;
    int right = (i + 1) % N;
    if(state[i] == HUNGRY && state[left] != EATING && state[right] != EATING) {
        state[i] = EATING;
        sem_post(&phil[i]); //0 -> 1
    }
}

void put_forks(int i) {
    int left = (i + N - 1) % N;
    int right = (i + 1) % N;
    sem_wait(&sync);
    state[i] = THINKING;
    test(left);
    test(right);
    sem_post(&sync);
}

void test(int i) {
    int left = (i + N - 1) % N;
    int right = (i + 1) % N;
    if(state[i] == HUNGRY && state[left] != EATING && state[right] != EATING) {
        state[i] = EATING;
        sem_post(&phil[i]);
    }
}