You are on page 1of 5

Classic Problems

! Studying concurrency in real-world applications is always difficult

Classic "

"
These applications have their own idiosyncrasies
They are often very large and it would take hours for us to understand

Concurrency
how they work
! So people have designed easy-to-understand applications that raise
relevant and challenging concurrency issues
Problems " Modeling “everyday life” situations
! We have looked at
" Producer / Consumer
ICS432 - Fall 2008 " Reader / Writer

Concurrent and High-Performance ! We’ll look at those in some detail (in pseudo-code, not Java)
" Savings Account (very simple)
Programming " Barbershop (still pretty easy)
" Dining Philosophers (difficult and very famous)
Henri Casanova (henric@hawaii.edu)

Shared Bank Account The Bank Account Monitor


! Consider a bank account shared by multiple Monitor BankAccount {
int total=0;
people cond more_money;
! There are two operations void deposit(int amount) { ! A very simple problem, very
" deposit(): adds money to the account total += amount; similar to producer / consumer
signal_all(more_money); ! An optimization would consist
" withdraw(): remove money } in having the minimum
! Should block if not enough money void withdraw(int amount) { withdrawal get priority
while (amount > total) {
! Let’s look at a solution using a monitor wait(cond);
} ! Remember that this is a
total -= amount monitor, thus with (implicit)
} mutual exclusion on the
} deposit() and withdraw()
methods

Bank Account The BarberShop Problem


! A very simple problem, very similar to ! A pretty simple problem also, just about thread communication
! The Barber provides a service (i.e., a haircut) to incoming customers
producer / consumer " opens the door to the shop
! An optimization would consist in having the " waits for a customer
" gives a haircut
minimum withdrawal get priority " tells the customer to leave
" waits until the customer has left through the back (it’s a polite barber)
! The Customer
" wait for the door to open
" enters the barber shop
" waits until the barber is available
" waits until the haircut is finished
" leaves the shop through the back door
! The problem: to develop a Barber Shop monitor
The BarberShop The BarberShop Monitor
! We must implement three methods
customer " getHaircut(): called by customers
has left " getNextCustomer(): called by the barber when free
customer getting barber " finishedCut(): called by the barber when done
a haircut
void Customer() {
void Barber() { BarberShop.getHaircut();
waiting customers while (true) { }
BarberShop.getNextCustomer();
<Cut hair> void Customer() {
BarberShop.getHaircut();
BarberShop.finishedCut(); }
}
} void Customer() {
BarberShop.getHaircut();
...
}

The BarberShop Implementation The BarberShop Monitor


! We use four boolean flags Monitor BarberShop {
" barber: IDLE / WORKING (barber) boolean barber = IDLE;
boolean chair = FREE;
" left: GONE / STILL_HERE (customer just serviced) boolean left = GONE;
" door: OPEN / CLOSED boolean door = OPEN;
" chair: OCCUPIED / FREE
cond chair_occupied;
! We use four condition variables cond customer_left;
" The barber waits on cond barber_available;
! chair_occupied: a customer just sat down (chair = OCCUPIED) cond door_open;
! customer_left: the recently served customer just left (left = GONE)
void getHaircut() { . . . }
" The customer waits on
! door_open: the entrance is open (door = OPEN) void getNextCustomer)() { . . . }
! haircut_done: the haircut is done (baber = IDLE)
void finishedCut() { . . . }
}

BarberShop.getHaircut() BarberShop with Monitors


void getHaircut() {
// wait for door to open ! Overall, a pretty natural solution but that
while (door == CLOSED) { void getNextCustomer() {
door_open.wait(); while(chair == FREE) { requires a bit of thoroughness
chair_occupied.wait();
}
door = CLOSED; }
! Different solutions are possible with different
} flags / condition variables
// make the barber non-idle
barber = WORKING;
" We decided arbitrarily who sets which variables,
chair = OCCUPIED; void finishCut() {
could be done otherwise
chair_occupied.signal(); barber = IDLE " Many solutions available on the Web
// wait for the barber to be idle haircut_done.signal();
while (barber == WORKING) { while (left = STILL_HERE) { ! One interesting exercise: How to implement
haircut_done.wait();
} }
customer_left.wait() this using only Semaphores?
chair = FREE;
left = GONE;
door = OPEN; ! Turns out is actually easier
door_open.signal();
}
customer_left.signal(); } ! Let’s look at it
BarberShop with Semaphores The Dining Philosophers Problem
! A classical synchronization problem
! We’re going to have one binary semaphore per “resource”:
" pretty meaningless at face value
" left: the “fact” that the last customer has left (init = 0) " but representative of many real-world problems
" barber: the barber (init = 0)
" door: the front door (init = 0)
! 5 philosophers sit at a table with
" chair: the chair (init = 0) void getNextCustomer() { 5 plates and 5 forks/chopsticks
V(door); // open the door ! Each philosopher does two
P(chair); // wait for chair to be taken things:
}
" think for a while
void getHaircut() {
P(door); // wait for door to be open " eat for a while

V(chair); // seat in the chair " repeat


void finishCut() {
P(barber); // wait for barber to be done ! To eat, a philosopher needs two
V(barber); // say “I am done”
V(left); // leave the shop forks/chopsticks
P(left); // leave the shop
}
}

Philosopher Algorithm “Protected” Forks


! We need to avoid two philosophers having the same chopstick in hand
! Ideas: Use an array of “locks”, one for each fork
void philosopher() { " Acquiring the lock means “getting the fork”
<think> " Releasing the lock means “giving up the fork”
pickupForks(); ! These are “conceptual” locks (e.g., may be something else in Java)
<eat>
phil #0
putdownForks();
}
locks[4] locks[0]

phil #4 phil #1 ! To eat, philosopher #i must


acquire lock[(i+4) % 5] and
lock[i]
! Question: how to implement the pickupForks() and locks[1]
locks[3]
putdownForks() methods?
" putdownForks() is actually straightforward
phil #3 phil #2
locks[2]

Implementation Idea #1 Solution #1


int right(int phil) { int left(int phil) {
return ((phil+4) % 5); phil #0 return ((phil +4) % 5); phil #0
} }
int left(int phil) { locks[4] locks[0] int right(int phil) { locks[4] locks[0]
return phil; return phil;
} phil #4 phil #1 } phil #4 phil #1

void pickupForks(int phil) { void pickupForks(int phil) {


lock(locks[left(phil)]); locks[3] locks[1] lock(locks[left(phil)]); locks[3] locks[1]
lock(locks[right(phil)]; lock(locks[right(phil)];
} }
phil #3 phil #2 phil #3 phil #2
locks[2] locks[2]
void putdownForks(int phil) { void putdownForks(int phil) {
unlock(locks[left(phil)]); unlock(locks[left(phil)]);
unlock(locks[right(phil)]); unlock(locks[right(phil)]);
} } what is wrong in this solution?
Solution #1 Deadlocks Solution #2
! If all philosophers pick up the fork on their left ! A simple Idea: make the solution asymmetrical
simultaneously and then try to pick up the fork on
" Odd-numbered philosophers start with the left fork
their right, then we have a deadlock
" Even-numbered philosophers start with the right fork
! The deadlock may happen very rarely on a single
proc system
" what are the odds that a thread is interrupted right in void pickupForks(int phil) {
between the two calls to pthread_lock() if (phil %2 == 0) {
! May happen more frequently on a multi-core lock(locks[right(phil)]);
lock(locks[left(phil)];
system } else {
! At any rate, one is never guaranteed that the code lock(locks[left(phil)]);
will not block at some point in time lock(locks[right(phil)];
" Think of a server that must stay up for months... }
}
! Question: What’s a deadlock-free implementation?

Solution #2 doesn’t Deadlock! Solution #2 isn’t so great...


phil #0
! If P1 gets to f1 before P2 ! Small possibility of starvation
" P2 does not pick up f2
locks[4] locks[0] " A philosopher could put down a fork and pick it right
" If P4 gets to f3 before P3
If P4 gets to f4 before P0 back up
!
phil #4 phil #1
"
"
P4 eats!
P0 doesn’t pick up f0
" But this depends upon the way in which threads are
" P1 eats implemented
... locks[1]
!
locks[3] " And requires that a philosopher’s think time could be
! This kind of exhaustive 0 seconds
reasoning is very tedious
! But we can see that at least phil #3
locks[2]
phil #2 ! Biggest problem: the implementation is unfair
two philosophers can always " One of the threads has an advantage over the others
eat no matter what
" Philosopher 0 doesn’t face a lot of competition when
! Formal reasoning for
something like this can be
picking up the fork on its right
very difficult " Let’s see this on a picture

Solution #2 is Unfair Towards a Fair Solution


Unfair advantage ! How can we not give an unfair advantage to Philosopher 0?
because of less 0 ! The problem is that it’s a jungle out there
" There is no communication between philosophers
competition
" They have their eyes on the forks, and not on each other

! New idea:
1 " When a philosopher wants to eat, he checks the forks

4 " If they are available, he eats

" otherwise, he waits on a condition variable

! one condition variable per philosopher


" when a philosopher finishes eating he checks to see if his
neighbors are waiting
" if so, he signals them so that they can recheck the forks

! Major difference: everything is about philosopher state not about


the forks
3 2 ! THINKING, HUNGRY, EATING
Solution #3 Solution #3 not that good...
void pickupForks(int phil) {
lock(mutex); // enter critical section
void putdownForks(int phil) { ! Risk of starvation
lock(mutex); // enter critical section " There could be a ping-pong effect
state[phil] = HUNGRY;
if (state[left(phil)] == HUNGRY) P0 and P2 get to eat
while ((state[left(phil)] == EATING) || !
signal(cond[left(phil)]);
(state[right(phil)] == EATING)) { ! P1 and P3 get to eat
if (state[right(phil)] == HUNGRY)
wait(cond[phil], mutex); ! P0 and P2 get to eat
signal(cond[right(phil)];
} ! ....
state[phil] = THINKING;
state[phil] = EATING; ! P4 never gets to eat!
unlock(mutex); // leave critical section
unlock(mutex); // leave critical section
}
} ! This is rare, but could happen in the long run
! It would be nice to have something that is
! One lock for mutual exclusion guaranteed to work well and fairly
! One array of condition variables, one per philosopher
! All philosophers are equal
! Still a problem :(

Solution #4: The Queue Solution #5: The Deli


! To guarantee fairness one can use a queue of philosophers ! Use numbers (the “Deli” model)
" When a philosopher finds that he cannot eat, he is placed on a " When hungry, a philosopher takes a number
queue " If a philosopher is hungry and so are his neighbors, the one with
" Only the philosopher at the head of the queue is allowed to eat the lowest number gets to eat
next " Numbers always increase
! Problem ! Works pretty well, but still can lead to poor performance with
" A philosopher could find that he can pickup forks BUT he is not too much blocking
at the head of the queue
! Some solutions use a mix of everything we’ve seen so far...
" In this case he has to wait
! It turns out that having a deadlock-free and fair solution is
" Hence philosophers cannot eat as much as they want
rather difficult in theory
" So it’s fair, but not very efficient
! Some of the solutions we have seen are good, but could
! Possible Solution potentially break down in particular situations
" Allows philosophers to jump ahead in the queue when they use
" Depending on thinking / eating times
forks that are not needed by anybody ahead of them in the
queue " Depending on the number of philosophers

Conclusion Conclusion
! Main things to worry about ! There are many solutions
" difficult to find the best one
" Deadlock
" especially because it depends on the “workload”
" Starvation ! 2 hungry philosophers, 7 not-so-hungry ones
" Fairness ! ...
! Lessons
! For some problems it can be very difficult to " One must worry about deadlock
come up with a good solution that works " One must worry about starvation
under all conditions ! enforce that each thread gets unblocked every now and then
(queue, numbers)
! can be detrimental to performance
" Worry about treating all threads equally (not always
needed, but often)

You might also like