300 likes | 489 Views
Concurrency , Dining Philosophers Lecture 15. COMP 201. What is a Concurrent Program?. A sequential program has a single thread of control.
E N D
What is a Concurrent Program? A sequential program has a single thread of control. A concurrent program has multiple threads of control allowing it perform multiple computations in parallel and to control multiple external activities which occur at the same time.
Why Concurrent Programming? • Performance gain from multiprocessing hardware • parallelism. • Increased application throughput • an I/O call need only block one thread. • Increased application responsiveness • high priority thread for user requests. • More appropriate structure • for programs which interact with the environment, control multiple activities and handle multiple events.
Do I need to know about concurrent programming? Concurrency is widespread but error prone. • Therac - 25 computerised radiation therapy machine Concurrent programming errors contributed toaccidents causing deaths and serious injuries. • Mars Rover Problems with interaction between concurrent taskscaused periodic software resets reducing availability forexploration.
Deadlock Concepts: system deadlock: no further progress four necessary & sufficient conditions Models: deadlock - no eligible actions Practice: blocked threads Aim: deadlock avoidance - to design systems where deadlock cannot occur.
Deadlock: four necessary and sufficient conditions • Serially reusable resources: the processes involved share resources which they use under mutual exclusion. • Incremental acquisition: processes hold on to resources already allocated to them while waiting to acquire additional resources. • No pre-emption: once acquired by a process, resources cannot be pre-empted (forcibly withdrawn) but are only released voluntarily. • Wait-for cycle: a circular chain (or cycle) of processes exists such that each process holds a resource which its successor in the cycle is waiting to acquire.
A E B C D Wait-for cycle Has A awaits B Has E awaits A Has B awaits C Has C awaits D Has D awaits E
3 2 2 1 3 4 1 4 0 0 Dining Philosophers Five philosophers sit around a circular table. Each philosopher spends his life alternately thinking and eating. In the centre of the table is a large bowl of spaghetti. A philosopher needs two forks to eat a helping of spaghetti. One fork is placed between each pair of philosophers and they agree that each will only use the fork to his immediate right and left.
Dining Philosophers - model structure diagram Each FORK is a shared resource with actions get and put. When hungry, each PHIL must first get his right and left forks before he can start eating.
ASML specification • A number of philosophers are sitting around a table. • Each one has a fork to the left and a fork to the right. • We model forks as structures with a unique field index. • structure Fork index as Integer
Abstract class Philosopher • Philosophers are modelled as having a unique index, what state they are currently in and as being capable of two methods: • reporting whether they can make a state change (canMove) and • performing a state change (move). • Because the value of the field status can change, a Philosopher is a class and not a structure. abstractclass Philosopher var status as State = Thinking index as Integer canMove() as Boolean move()
For simplicity we assume that there are a fixed number (four) of true philosophers (called simply philosophers below) and one fake philosopher called nobody. numPhilosophers as Integer = 4 nobody as Philosopher = undef Likewise we have four forks. numForks as Integer = numPhilosophers forks as Set of Fork = { Fork(i) | i ∈ {1..numForks} } The fork to the left of a philosopher has the same index as the philosopher. The fork to the right of a philosopher has the next higher index (modulo the number of philosophers). left(p as Philosopher) as Fork return Fork(p.index) right(p as Philosopher) as Fork return Fork(p.index mod numPhilosophers + 1)
Philosopher’s lifecycle • A thinking philosopher has no forks. (Who needs a fork to think?) • A thinking philosopher may become hungry. • A hungry philosopher tries to grab the fork to the left and thus becomes a hungry philosopher with a left fork. • But one fork is not enough: a philosopher starts eating only upon obtaining both forks. • The fork to right can be obtained only if it is not being used. • From eating, there is only one place to go: back to thinking after putting down both forks.
Thinking Hungry Eating HungryWithLeftFork A successful philosopher's lifecycle is this enum State Thinking; Hungry; HungryWithLeftFork; Eating Initially nobody has a fork. var holder as Map of Fork to Philosopher = { f ↦ nobody | f ∈ forks }
Greedy Philosophers • A greedy philosopher never puts down a fork until (s)he has eaten and starts thinking. • This can lead to deadlock. • The behaviour has been made a little fancier by introducing a random amount of thinkingand eating for a fixed amount of time • a thinking philosopher will remain thinking about 80 percent of the time.
class greedyPhilosopher extends Philosopher varbitesas Integer = 0 move() matchstatusThinking : if (any i | i ∈ {1..10}) < 3 then// usually they prefer to thinkstatus := Hungry Hungry : if holder(left(me)) = nobody then holder(left(me)) := mestatus := HungryWithLeftFork HungryWithLeftFork : if holder(right(me)) = nobody then holder(right(me)) := mestatus := Eating bites := 3 // the fixed number of bitesEating : ifbites> 0 thenbites := bites - 1 else holder(left(me)) := nobody holder(right(me)) := nobody status := Thinking
Extracting the conditions from the method move yields the function canMove which indicates whether the philosopher can make a state change or not. class greedyPhilosopher... canMove() as Boolean return status = Thinking ∨ (status = Hungry ∧ holder(left(me)) = nobody) ∨ (status = HungryWithLeftFork ∧ holder(right(me)) = nobody) ∨ status = Eating asString() as String return "Greedy #" + index
Generous Philosophers • A generous philosopherdoes not insist on following a successful philosophical life. • After picking up the left fork, but finding that the right fork is not available, a generous philosopher drops the left fork and goes back to think some more. • So if all philosophers are generous, then there is no deadlock, but starvation is possible.
class generousPhilosopher extends Philosopher move() matchstatusThinking : status := Hungry Hungry : if holder(left(me)) = nobody then holder(left(me)) := mestatus := HungryWithLeftFork HungryWithLeftFork : if holder(right(me)) = nobody then holder(right(me)) := mestatus := Eating else// someone else is holding the right fork put // the left one down and try again another time holder(left(me)) := nobody status := Thinking Eating : holder(left(me)) := nobody holder(right(me)) := nobody status := Thinking
Notice thatthe conditions which indicate whether a generous philosopher can make a state change or not are more liberal than those for greedy philosophers. class generousPhilosopher... canMove() as Boolean return status = Thinking ∨ (status = Hungry ∧ holder(left(me)) = nobody) ∨ status = HungryWithLeftFork ∨ status = Eating asString() as String return "Generous #" + index
Thinking Hungry Eating HungryWithLeftFork A successful generousphilosopher's lifecycle is this
The Scheduler Here is one possible scheduler: • From the set that it is given, • it chooses a philosopher that can make a state transition and then • fires the state transition. • If no philosopher can make a step, then • the system is deadlocked and • an exception is thrown.
structure deadlockException implements RuntimeException message as String describe()as String return "Deadlock: " + message schedule(ps as Set of Philosopher, i as Integer) choose p ∈ ps where p.canMove() step currentStatus = p.status p.move() step WriteLine(p + " was " + currentStatus + ", but now is " + p.status) ifnonethrow deadlockException("after " + i + " steps")
The Main Program • The main program tries to run the above schedule 1000 times, and is ready to catch the exception thrown if the system deadlocks. • You may choose which type of philosopher to schedule, just comment out one of the "greedy" or "generous" lines in the code.
The Main Program (code) Main() phils = { new greedyPhilosopher(i) as Philosopher | i ∈ [1..numPhilosophers] } //phils = { new generousPhilosopher(i) as Philosopher | i ∈ [1..numPhilosophers] } trystepforeachi∈ [1..1000] schedule( phils, i ) catch d as deadlockException : WriteLine(d.describe())
Summary • Concepts • deadlock: no futher progress • four necessary and sufficient conditions: • serially reusable resources • incremental acquisition • no preemption • wait-for cycle • Models • no eligable actions (analysis gives shortest path trace) • Practice • blocked threads Aim: deadlock avoidance - to design systems where deadlock cannot occur.