Title: Dining Philosophers
1Dining Philosophers Monitors
UNIVERSITY of WISCONSIN-MADISONComputer Sciences
Department
CS 537Introduction to Operating Systems
Andrea C. Arpaci-DusseauRemzi H.
Arpaci-Dusseau Haryadi S. Gunawi
- Questions answered in this lecture
- How to synchronize dining philosophers?
- What are monitors and condition variables?
- What are the differences between Hoare and Mesa
specifications?
2Dining Philosophers
- Problem Definitions
- N Philosophers sitting at a round table
- Each philosopher shares a chopstick with neighbor
- Each philosopher must have both chopsticks to eat
- Neighbors cant eat simultaneously
- Philosophers alternate between thinking and
eating - Each philosopher/thread i runs following code
- while (1)
- think()
- take_chopsticks(i)
- eat()
- put_chopsticks(i)
-
3Dining Philosophers Attempt 1
- Two neighbors cant use chopstick at same time
- Must test if chopstick is there and grab it
atomically - Represent each chopstick with a semaphore
- Grab right chopstick then left chopstick
- Code for 5 philosophers
- sem_t c 5 // c for chopstick
- // Initialize each to 1
- take_chopsticks(int i)
- wait(ci) // take chopstick on your
right - wait(c(i1)5) // take chopstick on your left
-
- put_chopsticks(int i)
- signal(ci) // put chopstick on your
right - signal(c(i1)5) // put chopstick on your
left -
- What is wrong with this solution? Deadlock!
- Everyone picks up the left chopstick first
4Dining Philosophers Attempt 2
- Approach
- Grab lower-numbered chopstick first, then
higher-numbered - Code for 5 philosophers
- take_chopsticks(int i)
- if (i lt 4)
- wait(ci) // take the right chopstick
first - wait(ci1) //
- else
- wait(c0) // take the left chopstick first
- wait(c4)
-
-
- What is wrong with this solution? Not parallel
enough! - P0 P3 take the right chopsticks first (C0 to
C3) - P4 cannot take the left chopstick (C0) because P0
already takes C0 - That implies P4 cannot take C4, which implies C4
can be taken by P3 - Result only 1 process (P3) can eat but the other
4 processes are waiting! - Since there are 5 chopsticks in the room, actuall
it is possible to have 2 processes eat at the
same time!
5Dining Philosophers How to Approach
- Guarantee two goals
- Safety Ensure nothing bad happens (dont violate
constraints of problem) - Liveness Ensure something good happens when it
can (make as much progress as possible) - Introduce state variable for each philosopher i
- statei THINKING, HUNGRY, or EATING
- Safety No two adjacent philosophers eat
simultaneously - for all i !(stateiEATING
statei15EATING) - Liveness Not the case that a philosopher is
hungry and his neighbors are not eating - for all i !(stateiHUNGRY
(statei45!EATING statei15!EATING))
6Dining Philosophers Solution
- sem_t mayEat5 // how to initialize?
- sem_t mutex // how to init?
- int state5 THINKING
- take_chopsticks(int i)
- wait(mutex) // enter critical section
- statei HUNGRY
- testSafetyAndLiveness(i) // check if I can run
- signal(mutex) // exit critical section
- wait(mayEati)
-
- put_chopsticks(int i)
- wait(mutex) // enter critical section
- statei THINKING
- test(i1 5) // check if neighbor can run now
- test(i4 5)
- signal(mutex) // exit critical section
-
- testSafetyAndLiveness(int i)
- if (stateiHUNGRY statei45!EATINGsta
tei15!EATING)
7Recap Semaphore and Synchronization problems
- Two Classes of Synchronization Problems
- Uniform resource usage with simple scheduling
constraints - No other variables needed to express
relationships - Use one semaphore for every constraint
- Examples thread join and producer/consumer
- Complex patterns of resource usage
- Cannot capture relationships with only semaphores
- Need extra state variables to record information
- Use semaphores such that
- One is for mutual exclusion around state
variables - One for each class of waiting
- Examples readers/writers, and dining
philosophers - Always try to cast problems into first, easier
type
8Locks/Semaphores Not Convenient
- Problems
- Must initialize semaphore properly
- Last time semaphore can be set to 0, 1, or N
- Misordering of locks/semaphores can be a problem
- func1() wait(a) wait(b) . / do job /
signal(b) signal(a) - func2() wait(b) wait(a) . / do job /
signal(a) signal(b) - Deadlock!
- Users can inadvertently misuse locks and
semaphores - Ex forget to call signal(a) after wait(a)
- Solution
- Build another higher-level software primitive
Monitors
9Monitors
- Idea
- 1st part Provide language support to
automatically lock and unlock monitor lock when
in critical section - Lock is added implicitly never seen by user
- 2nd part Provide condition variables for
scheduling constraints - Examples
- Mesa language from Xerox
- Java from Sun
- Use synchronized keyword when defining method
- synchronized deposit(int amount)
- balance amount
10Example
With semaphore
With monitor
- Monitor ProducerConsumer
- doProduce()
- // produce an item
-
- doConsume()
- // consume an item
-
-
- void producer()
- while(1)
- doProduce()
-
- void consumer()
- while(1)
- doConsume()
- void producer()
- while(1)
- wait(mutex)
- // produce an item
- signal(mutex)
-
-
- void consumer()
- while(1)
- wait(mutex)
- // remove an item
- signal(mutex)
-
- What if buffer is empty/full?
- Produce needs to wait if buffer is full
- Consumer needs to wait if buffer is empty
- Dont want to use semaphore, so we need new
mechanism contidion variables
11Condition Variables
- Idea
- Used to specify scheduling constraints
- Always used with a monitor lock
- No value (history) associated with condition
variable - Allocate Cannot initialize value!
- Must allocate a monitor lock too (implicit with
language support, explicit in POSIX and C) - Three operations on a condition variable
- wait(cv)
- Call with monitor lock held Releases monitor
lock Sleeps until signalled Reacquires lock
when woken - Hence, for each cv, the monitor keeps a queue
- NOTE (unlike semaphores wait(sem), there is no
test in monitors wait(cv). - In monitor, when you call wait(cv), you will
always go to sleep! - In semaphore, it depends on the semaphore value!
12Condition Variables
- signal(cv) or notify(cv)
- Call with monitor lock held
- Wake one thread waiting on this condition
variable (if any) - If no thread is waiting, signal is discarded
- NOTE no history (unlike semaphore value)
- broadcast(cv) (or NotifyAll)
- Wake all threads waiting on condition variable
13Using condition variables
void producer() while(1)
doProduce() void consumer() while(1)
doConsume()
Monitor ProducerConsumer condition full,
empty doProduce() if (n10)
wait(full) // put an element (n) if
(n1) signal(empty) doConsume()
if (n0) wait(empty) // remove an
element (n--) if (n9) signal(full)
Note n and n-- implicitly protected by the
monitor lock. Hence, no need to add mutex
semaphore to guard n
14Implementation of Signals Hoare vs. Mesa
- Hoare (signal-and-exit)
- Signaller relinquishes the CPU and gives the lock
to the waiter - Problem
- If sharing is between processes, context-switch
too often! - Signaller might have something more todo
- Complex to implement (signal() must be the last
instruction) - Mesa (signal-and-continue)
- Signaller can keep lock and CPU (Practice)
- Easy to implement
- But, the state that a thread is waiting on may
not be true when the awaken process!
15Hoare/Mesa
Monitor ProducerConsumer condition full,
empty doProduce() a acquire(lock) b
if (n10) c wait(full) d // put an
element e if (n1) f signal(empty) g
// do other stuffs h release(lock)
doConsume() s acquire(lock) t if
(n0) u wait(empty) v // remove an
element w if (n9) x signal(full) y
// do other stuffs z release(lock)
Hoare 2s 2t 2u 1a 1b 1d 1e 1f 2v 2w
Mesa 2s 2t 2u 1a 1b 1d 1e 1f 1g 1h 2v
2w The idea is at 1f, process 2 is removed from
the wait queue. After 1h ends, even the OS does
not guarantee that 2v will run directly. Whats
the problem? What will happen in this
scenario Mesa (1 producer, 2 consumers) 2s 2t
2u 1a 1b 1d 1e 1f 1g 1h 3s 3t 3u 3v 3w 3x 3y 3z
2v 2w Thread3 (a consumer) already consumes the
item that thread1 produces! When thread2 is at
2v, there is no item to work on!
16Mesa
Monitor ProducerConsumer condition full,
empty doProduce() a acquire(lock) b
while (n10) c wait(full) d // put an
element e if (n1) f signal(empty) g
// do other stuffs h release(lock)
doConsume() s acquire(lock) t while
(n0) u wait(empty) v // remove an
element w if (n9) x signal(full) y
// do other stuffs z release(lock)
- Mesa
- A process is waiting/sleeping because of a
condition (e.g. producer sleeps because n 0) - When a process is awaken up, that condition might
not be true anymore - Hence, need to put a while loop!
- while ( no resource )
- wait (cv)
- Mesa (1 producer, 2 consumers)
- 2s 2t 2u 1a 1b 1d 1e 1f 1g 1h 3s 3t 3u 3v 3w 3x
3y 3z 2t ltsleepgt - Hence thread 2 cannot proceed again.
17Summary
- Locks
- Mutual exclusion only
- Semaphore
- Mutual exclusion
- Scheduling
- Good for scheduling
- Bad error prone if used for locking
(initialization, ordering) - Monitors
- Good for mutual exclusion (implicit) and
scheduling