## The dining philosophers problem

• A bunch (`N`) of philosophers who spend their time thinking and eating

• A philosopher:

• Love Asian food! (chopsticks not fork and knife)
• Only `N` chopsticks on the table (funding cuts)
• Cannot eat with only one chopstick
• Can only take the chopsticks on the side
• Write a program which simulates the behavior of the philosophers

• What is the purpose?

• Classical problem illustrating

• Mutex: only one philosopher at a time may have a chopstick
• Conditional synchronisation: may eat only when has two chopsticks
• Starvation

## First attempt

• Let's start from the top-level (Code here)

```      public Table1(int m) {
MAX = m ;                     // How many are sit
chops = new Semaphore[MAX] ;  // Chopsticks
phil  = new Phil1[MAX] ;      // Philosophers
for (int i = 0 ; i < MAX ; i++)
chops[i] = new Semaphore(1,true) ;
for (int i = 0 ; i < MAX ; i++)
phil[i] = new Phil1(i,MAX,chops) ;
}

public void run () {
for (int i = 0 ; i < MAX ; i++)
phil[i].start() ;
}
```
• We model chopsticks as semaphores

• Taken, the semaphores is `0`
• Available, the semaphore is `1`
• The code for philosophers

```  public void run () {
int left = i ;
int right = (i+1)%MAX;
while (true) {

System.out.println("Philisopher "+i+" is thinking");
catch (InterruptedException e) {} ;

try {
chops[left].acquire() ;
chops[right].acquire() ;
} catch (InterruptedException e) {}

System.out.println("Philisopher "+i+" is eating");
catch (InterruptedException e) {} ;

chops[left].release() ;
chops[right].release() ;
}
}
```
• Does it work?

• If all manage to pick up their left-hand fork at about the same time then a deadlock occurs

```chops[left].acquire() ;
catch (InterruptedException e) {} ;
chops[right].acquire() ;
```
• In general deadlock occurs because there is a circular waiting

## Solution 1 : Break the symmetry

• Prevention by not allowing the circular waiting to arise.

• One of the philosophers must break the cycle

• We change the code for philosophers and tables

```if (i == 0)
{  try {
forks[right].acquire() ;
forks[left].acquire() ;
} catch (InterruptedException e) {}
}
else
{  try {
forks[left].acquire() ;
forks[right].acquire() ;
} catch (InterruptedException e) {}
}
```

• Starvation? Starvation is avoided with fair semaphores.

## Solution 2 : Limit the usage of resources

• If at most N-1 philosophers are eating then at least one will always have two forks.

• Use a general semaphore to represent N-1 available chairs (`chairs`)

```try {
chairs.acquire() ;
chops[left].acquire() ;
chops[right].acquire() ;
} catch (InterruptedException e) {}
```
```chops[left].release() ;
chops[right].release() ;
chairs.release() ;
```
• Starvation? Starvation is avoided with fair semaphores.

## Starvation

• Starvation with a fair scheduler?

• Depends on the implementation of semaphores!
• Blocked queue (FIFO) guarantees fairness
• Other “queue” policy might not
• Java?

• Default constructor `Semaphore`: no
• `Semaphore(int permits, boolean fair)`
• Why would you ever want to use a non-fair semaphore in Java? Because it might be faster.

## Preventing deadlocks: Order of resources

• One simple method for guaranteeing deadlock freedom when using locks for mutex

• Fix a (linear) order for resources. Only allowed to possess multiple resources if they are acquired in that order

• Solution 1 for the philosophers' problem

## Preventing deadlock: Break the chain

• Identify the “circular waiting chains”

• Don’t allow enough processes to “fill” the chain

• Solution 2 for the philosophers' problem

## Producer-Consumer

• Producer-consumer relationships between processes are a very common pattern

• Problem: how do we allow for different speeds of production vs consumption?

## Unbounded buffers

• Infinite bar!

• Producer can work freely

• Consumer must wait for producer

...

## Bounded buffers

• Real life!

• Producer must wait if buffer is full

• Consumer must wait if buffer is empty

## One-slot buffer

• Two reasons to block:

• Producers wait for an (the) empty slot to be available

• Consumers wait for a filled slot to be available

• Any ideas?

```  public void put(E e) {
< await empty > 0: empty-- ; buf = e >
full++ ;
}

public E get() throws InterruptedException {
E result ;
< await full == 1: full-- ; result = buf >
empty++ ;
return result;
}
```
• How do we implement < B : S >

• We use semaphores
• Initialization

• One empty slot
```empty = new Semaphore (1,true) ;
```
• Zero full slots
```full = new Semaphore (0,true) ;
```
• Invariant

• `empty + full <= 1`
• One-slot code

```  public void put(E e) throws InterruptedException {
empty.acquire();
buf = e;
full.release();
}

public E get() throws InterruptedException {
E result ;
full.acquire();
result = buf ;
empty.release();
return result;
}
```
• A producer and consumer with different speeds

• Producer
```for (int i = 0 ; i < P ; i++) {
catch (InterruptedException e) {} ;

System.out.println("Produced"+i);

try { buf.put(i) ; }
catch (InterruptedException e) {}
}
```
- Consumer
```for (int i = 0 ; i < C ; i++) {
catch (InterruptedException e) {} ;

System.out.print("Consumed ");

try { d = buf.get() ; }
catch (InterruptedException e) {}

System.out.println(d);
}
```
• Here, you can see the code that puts all the pieces together

## General N-slot buffer

code

• Producer(s) add to the back (tail) of the buffer

• Consumer(s) take from the front (head) of the buffer

• Count the number of free/full slots as before

• Assume that the buffer has size `S`

• How should I initialize the `empty` and `full` semaphores?

```empty = new Semaphore (S,true) ;
full = new Semaphore (0,true) ;
```
• Invariant

• `empty + full <= S`
• Single producer

```public void put(E e) throws InterruptedException {
empty.acquire();
buf[front] = e;
front = (front+1)%S;
full.release();
}
```
• Single consumer

```public E take() throws InterruptedException {
full.acquire();
E result = buf[rear];
rear = (rear+1)%S;
empty.release();
return result;
}
```
• Does it work?

• `front` is shared and updated among producers
• `rear` is shared and updated among consumers
• Share-update problem again! How did we solve it?

• Mutex!

• How many?

• `mutexP`: a mutex for producers

```public void put(E e) throws InterruptedException {
empty.acquire();
mutexP.acquire();
buf[front] = e;
front = (front+1)%S;
mutexP.release();
full.release();
}
```

• Can we interchange lines 2 and 3?

• Can we interchange lines 6 and 7?

• `mutexC`: a mutex for consumers

```public E take() throws InterruptedException {
full.acquire();
mutexC.acquire();
E result = buf[rear];
rear = (rear+1)%S;
mutexC.release();
empty.release();
return result;
}
```

• Can we interchange lines 2 and 3?

• Can we interchange lines 6 and 7?

## Preparing for blocking

• A call to `s.acquire()` may involve blocking for a long time

• A process might need to take precautions before waiting (e.g. set controlled device in safe state)

• Problem: Precautions unnecessarily taken also when no waiting occurs

```take_precautions() ;
s.acquire() ;
```
• If there is no need for blocking, `take_precautions()` has been called for no reason (it might be expensive)
• Another operation on semaphores

• `boolean tryAcquire()`

• A non-blocking operation acquiring the semaphore (and returns true) if it is possible at time of invocation. Otherwise, returns false (without acquiring the semaphore).

• Ignores fairness setting!

• Another classic synchronisation problem

• Writers examine and modify
• A writer must have mutex
• Invariant

• ( nr==0 ∨ nw==0 ) ∧ nw <= 1
• Any ideas?

```< await (nw==0): nr++; >
< nr-- ; >
```
```< await (nr==0 && nw==0): nw++; >
// write database
< nw--; >
```
• Generally speaking, how to implement a general `await` statement using semaphores?

• Technique "passing the baton"

• Not easy to understand (read it from the book!)

## Summary

• Philosophers' problem
• Synchronization patterns

• Producers-Consumers

• Readers-Writers (shortly, more comes later)

• Semaphores: extra operation `tryAcquire`

• Semaphores pros

• Simple, efficient, expressive

• Passing the baton – any await statement

• Semaphores cons

• Low level, unstructured

• Omit a `release`: deadlock
• Omit a `acquire`: failure of mutex
• Synchronisation code not linked to the data

• Synchronisation code can be accessed anywhere, but good programming style helps!

Concurrent Programming 2016 - Chalmers University of Technology & Gothenburg University