Lecture 7: Concurrency: Deadlocks

Previous lecture Next lecture

Exam

Problems using synchronization and possible solutions

Important questions:

Deadlocks

Why is this happening?

Deadlocking processes

The term deadlock (in computer science) means

[...] A situation in which two or more processes are unable to process because each is waiting for one of the others to do something.

Conditions for deadlocks

All of the following three conditions must be fulfilled for a deadlock to occur:

  1. Exclusive allocation of resources ("mutual exclusion")

resource unit that has been allocated to another process 2. Allocation of additional resources ("hold and wait")

other resources 3. No removing of resources ("no preemption")

allocated 4. Only if an additional condition occurs at runtime, we really have a deadlock:

one resource needed by the next process in the chain

Resources...

are administered by the operating system and provided to the processes. There are two kinds of resources:

Reusable resources

Consumable resources

Resource allocation graphs

... are used to visualize and also automatically detect deadlock situations

Classic deadlock: dining philosophers

Five philosophers spend their life either thinking or eating. And they love eating spaghetti! [1] To eat, the philosophers sit around a round table. Thinking makes you hungry – every philosopher has to eat! To eat spaghetti, a philosopher needs both forks next to her or his plate!

Deadlocked philosophers?

Here, the three first necessary conditions are fulfilled:

Dining philosophers: version 1

/* all philosophers are
 concurrent... */
void phil (int who) {
  while (1) {
    think();
    grab(who);
    eat();
    drop(who);
  }
}
void think () { ... }
void eat () { ... }
semaphore fork[NPHIL] = {
  {1, NULL}, ...
};
void grab (int who) {
  wait(&fork[who]);
  wait(&fork[(who+1)%NPHIL]);
}
void drop (int who) {
  signal(&fork[who]);
  signal(&fork[(who+1)%NPHIL]);
}

Using a semaphore guarantees mutual exclusion when accessing the forks. By tradition, every philosopher first takes the right and then the left fork.

Dining philosophers: version 2

The problem in version 1 was the consequence of a process switch between the 1. und 2. wait – a critical section. Version 2 protects this critical section using mutual exclusion.

semaphore mutex = {1, NULL};
void grab (int who) {
  wait(&mutex);
  wait(&fork[who]);
  wait(&fork[(who+1)%NPHIL]);
  signal(&mutex);
}

Dining philosophers: version 3

const int N = 5; /* Number of philosophers */
semaphore mutex = {1, NULL}; /* Mutual exclusion */
semaphore s[N] = {{0, NULL},...}; /* one semaphor per philos. */
enum { THINKING, EATING, HUNGRY } status[N]; /* Philos. state*/
int left(i) { return (i+N-1)%N; } /* Index left neighbor */
int right(i) { return (i+1)%N; } /* Index right neighbor */
void test (int i) {
  if (state[i] == HUNGRY && state[left(i)] != EATING &&
    state[right(i)] != EATING) { // Can only start eating if no neighbour is eating
    state[i] = EATING;
    signal(&s[i]);
  }
}
void grab(int i) {
  wait(&mutex);
  state[i] = HUNGRY;
  test(i);
  signal(&mutex);
  wait(&s[i]);
}
void drop(int i) {
  wait(&mutex);
  state[i] = THINKING;
  test(left(i)); // Signal to neighbours that they might be able to start eating
  test(right(i));
  signal(&mutex);
}

This solution is deadlock free and has the maximum degree of concurrency.

Discussion: dining philosophers

Preventing deadlocks

Preventing deadlocks (2)

approaches:

Safe/unsafe states

(using the dining philosophers example)

Safe/unsafe states (2)

(using the example of multiple instances of resources)

Detection: Banker's algorithm

Deadlock detection

Deadlock resolution

Recovery phase after the detection phase

Discussion of prevention methods

βž™ Prevention methods more commonly used & relevant in practice

Conclusion