A bunch (N
) of philosophers who spend their time thinking and eating
A philosopher:
N
chopsticks on the table (funding cuts)Write a program which simulates the behavior of the philosophers
What is the purpose?
Classical problem illustrating
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
0
1
public void run () { int left = i ; int right = (i+1)%MAX; while (true) { System.out.println("Philisopher "+i+" is thinking"); try { Thread.sleep(500+(int)(Math.random()*1000)) ;} catch (InterruptedException e) {} ; try { chops[left].acquire() ; chops[right].acquire() ; } catch (InterruptedException e) {} System.out.println("Philisopher "+i+" is eating"); try { Thread.sleep(500+(int)(Math.random()*1000)) ;} 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() ; try { Thread.sleep(2000) ;} catch (InterruptedException e) {} ; chops[right].acquire() ;
In general deadlock occurs because there is a circular waiting
Prevention by not allowing the circular waiting to arise.
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) {} }
Deadlock?
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 (chops[MAX]
)
try { chops[MAX].acquire() ; chops[left].acquire() ; chops[right].acquire() ; } catch (InterruptedException e) {}
chops[left].release() ; chops[right].release() ; chops[MAX].release() ;
Starvation with a fair scheduler?
Java?
Semaphore
: noSemaphore(int permits, boolean fair)
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
Identify the “circular waiting chains”
Don’t allow enough processes to “fill” the chain
Producer-consumer relationships between processes are a very common pattern
Problem: how do we allow for different speeds of production vs consumption?
Infinite bar!
Producer can work freely
Consumer must wait for producer
...
Real life!
Producer must wait if buffer is full
Consumer must wait if buffer is empty
Multiple producer threads
Multiple consumer threads
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 >
Initialization
empty = new Semaphore (1,true) ;
full = new Semaphore (0,true) ;
Invariant
empty + full <= 1
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
for (int i = 0 ; i < P ; i++) { try { Thread.sleep(Speed) ;} catch (InterruptedException e) {} ; System.out.println("Produced"+i); try { buf.put(i) ; } catch (InterruptedException e) {} }- Consumer
for (int i = 0 ; i < C ; i++) { try { Thread.sleep(Speed) ;} catch (InterruptedException e) {} ; System.out.print("Consumed "); try { d = buf.get() ; } catch (InterruptedException e) {} System.out.println(d); }
Producer(s) add to the head (front) of the buffer
Consumer(s) take from the tail (rear) 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?
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() ;
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
Two kinds of threads share access to a “database”
Invariant
Any ideas?
< await (nw==0): nr++; > // read database < 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!)
How to avoid deadlock
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
release
: deadlockacquire
: failure of mutexSynchronisation code not linked to the data
Synchronisation code can be accessed anywhere, but good programming style helps!