e990e9ff0eb3d12a8187d959a373a5f5.ppt
- Количество слайдов: 41
CPS 110: Ordering constraints Landon Cox January 22, 2008
Too much milk solution leave note. Landon while (note. Melissa){ do nothing } if (no. Milk){ buy milk; } remove note. Landon leave note. Melissa if (no note. Landon){ if (no. Milk){ buy milk; } } remove note. Melissa
Too much milk “w/o waiting”? lock () if (no. Note && no. Milk){ leave note “at store” Not holding unlock () buy milk lock () remove note unlock () } else { unlock () } lock () if (no. Note && no. Milk){ leave note “at store” unlock () buy milk lock () remove note unlock () } else { unlock () } Only hold lock while handling shared resource.
Review of invariants ê What is an invariant? ê A “consistent/sane state” ê Something that is “always” true ê When can an invariant be broken? ê Can only be broken while lock is held ê And only by thread holding the lock ê Really a “public” invariant ê What is the data’s state in when the lock is free ê Like having a room tidy before guests arrive ê Hold a lock whenever manipulating shared data
More on invariants ê What about reading shared data? ê Still must hold lock ê Else another thread could break invariant ê (Thread A prints Q as Thread B enqueues) ê Hold a lock whenever reasoning about shared data
Intro to ordering constraints ê Say you want dequeue to wait while the queue is empty ê Can we just busy-wait? ê No! ê Still holding lock dequeue () { lock (q. Lock); element=NULL; while (head==NULL) {} // remove head element=head->next; head->next=NULL; unlock (q. Lock); return element; }
Release lock before spinning? What can go wrong? Another dequeuer could “steal” our element Head might be NULL when we try to remove entry dequeue () { lock (q. Lock); element=NULL; unlock (q. Lock); while (head==NULL) {} lock (q. Lock); // remove head element=head->next; head->next=NULL; unlock (q. Lock); return element; }
One more try ê Does it work? ê Seems ok ê Why? ê Sh. S protected ê What’s wrong? ê Busy-waiting ê Wasteful dequeue () { lock (q. Lock); element=NULL; while (head==NULL) { unlock (q. Lock); } // remove head element=head->next; head->next=NULL; unlock (q. Lock); return element; }
Ideal solution ê Would like dequeueing thread to “sleep” ê Add self to “waiting list” ê Enqueuer can wake up when Q is non-empty ê Problem: what to do with the lock? ê Why can’t dequeueing thread sleep with lock? ê Enqueuer would never be able to add
Release the lock before sleep? enqueue () { acquire lock find tail of queue add new element if (dequeuer waiting){ remove from wait list wake up dequeuer } release lock } dequeue () { acquire lock … if (queue empty) { release lock add self to wait list sleep } … release lock } Does this work?
Release the lock before sleep? 2 enqueue () { acquire lock find tail of queue add new element if (dequeuer waiting){ remove from wait list wake up dequeuer } release lock } dequeue () { acquire lock … if (queue empty) { release lock add self to wait list sleep } … release lock } 1 3 Thread can sleep forever Other problems? Wait list is shared and unprotected. (bad idea)
Release the lock before sleep? enqueue () { acquire lock find tail of queue add new element if (dequeuer waiting){ remove from wait list wake up dequeuer } release lock } dequeue () { acquire lock … if (queue empty) { add self to wait list release lock sleep } … release lock }
Release the lock before sleep? 2 enqueue () { acquire lock find tail of queue add new element if (dequeuer waiting){ remove from wait list wake up dequeuer } release lock } dequeue () { acquire lock … if (queue empty) { add self to wait list release lock sleep } … release lock } 1 3 Problem: missed wake-up Note: this can be fixed, but it’s messy
Two types of synchronization ê As before we need to raise the level of abstraction 1. Mutual exclusion ê One thread doing something at a time ê Use locks 2. Ordering constraints ê Describes “before-after” relationships ê One thread waits for another ê Use monitors
Course administration ê Discussion sections ê Everything going smoothly? ê Homework problems ê First set posted on Friday ê Go over in discussion section ê Students will present solutions
Course administration ê Project 0 ê 6/10 have perfect scores ê If you are struggling with P 0, think about why ê ê ê C++ language/Linux environment issues? Coding before thinking? Not reading the spec closely enough? Time management (i. e. not starting early enough)? Group management? ê Fix any non-language issues before Project 1
Course administration ê Project 1 is out today (due February 21 st) ê Project 1 data structures ê STL (less coding , more learning) ê Roll-your-own (less learning, more coding) ê Either is acceptable ê Reminder: office hours (come talk to us!) ê ê Me: 1 -3 on Wednesdays in D 304 Amre: 11: 00 – 1: 00 on Tuesdays in North Bldg N 001 Quinn: 12: 00 – 2: 00 on Mondays in Teer cluster Extra hours near project deadline ê Other questions or concerns?
Monitor synchronization 1. Mutual exclusion ê One thread doing something at a time ê Use locks 2. Ordering constraints ê Describes “before-after” relationships ê One thread waits for another ê Monitor: a lock + its condition variable
Locks and condition variables ê Condition variables ê Lets threads sleep inside a critical section ê Internal atomic actions (for now, by definition) // begin atomic release lock put thread on wait queue go to sleep // end atomic ê CV State = queue of waiting threads + one lock
Condition variable operations Lock always held wait (){ release lock put thread on wait queue go to sleep // after wake up acquire lock } Atomic Lock usually held signal (){ wakeup one waiter (if any) } Atomic Lock usually held broadcast (){ wakeup all waiters (if any) } Atomic Lock always held
CVs and invariants ê User programs ê Ok to leave invariants violated before wait? ê No: wait can release the lock ê Larger rule about returning from wait ê Lock may have changed hands ê State can change between wait entry and return ê Don’t make assumptions about shared state
Multi-threaded queue enqueue () { acquire lock dequeue () { acquire lock find tail of queue add new element if (queue empty) { wait (lock, CV) } signal (lock, CV) remove item from queue release lock return removed item release lock } } What if “queue empty” takes more than one instruction? Any problems with the “if” statement in dequeue?
Multi-threaded queue enqueue () { acquire lock dequeue () { acquire lock find tail of queue add new element if (queue empty) { // begin atomic wait release lock sleep and wait // end atomic wait re-acquire lock signal (lock, CV) release lock } } remove item from queue release lock return removed item }
Multi-threaded queue enqueue () { acquire lock 2 find tail of queue add new element signal (lock, CV) release lock } dequeue () { acquire lock … return removed item } 3 dequeue () { acquire lock 1 4 if (queue empty) { // begin atomic wait release lock sleep and wait // end atomic wait re-acquire lock } remove item from queue release lock return removed item }
Multi-threaded queue enqueue () { acquire lock dequeue () { acquire lock find tail of queue add new element if (queue empty) { // begin atomic wait release lock sleep and wait // end atomic wait re-acquire lock signal (lock, CV) release lock } How to solve? } remove item from queue release lock return removed item }
Multi-threaded queue enqueue () { acquire lock The “condition” in condition variable dequeue () { acquire lock find tail of queue add new element while (queue empty) { wait (lock, CV) } signal (lock, CV) remove item from queue release lock return removed item release lock } } Solve with a while loop (“loop before you leap”) You can now do the P 1 disk scheduler …
Mesa vs. Hoare monitors ê So far, we’ve described Mesa monitors ê After waking up, no special priority ê Threads have to recheck condition ê Hoare semantics are “simpler” ê But complicate implementation
Hoare semantics ê Condition guaranteed true after wakeup ê i. e. no need to loop ê Waiter acquires lock before any threads run ê (since lock protects condition) ê Including the signaler! ê Signaler must give up lock and signal atomically ê What would this mean for invariants? ê All invariants must be established before signal ê We will use Mesa semantics
Tips for using monitors 1. 2. List the shared data needed for problem Figure out the locks ê 1 lock per group of shared data 3. 4. Bracket code that uses shared data with lock/unlock Think about before-after conditions ê ê ê 1 condition variable per type of condition CV’s lock should protect data used to evaluate condition Call wait when you need to wait on a condition Loop to re-check condition when wait returns Call signal when condition changes Ensure invariants are established when lock is not held (unlock, wait)
Producer-consumer problem ê Producer makes something consumer wants ê Goal: avoid lock-step (direct hand-off) Soda drinker (consumer) Delivery person (producer) Problem: everyone’s time is wasted.
Producer-consumer problem ê Instead, use a fixed-size, shared buffer ê Producer puts in (must wait when full) ê Consumer takes out (must wait when empty) ê Must synchronize access to buffer ê Examples ê Unix pipes: cpp | cc 1 | cc 2 | as ê Interaction between hardware devices/buffers ê Project 1 disk scheduler
Use a soda machine as a buffer Delivery person (producer) Soda drinker (consumer) Vending machine (buffer)
Solving producer-consumer 1. What are the variables/shared state? ê Soda machine buffer ê Number of sodas in machine (≤ Max. Sodas) 2. Locks? ê 1 to protect all shared state (soda. Lock) 3. Mutual exclusion? ê Only one thread can manipulate machine at a time 4. Ordering constraints? ê Consumer must wait if machine is empty (CV has. Soda) ê Producer must wait if machine is full (CV has. Room)
Producer-consumer code consumer () { lock (soda. Lock) producer () { lock (soda. Lock) while (num. Sodas == 0) { wait (soda. Lock, has. Soda) } take soda out of machine add soda to machine signal (has. Room) signal (has. Soda) unlock (soda. Lock) } while(num. Sodas==Max. Sodas){ wait (soda. Lock, has. Room) } unlock (soda. Lock) }
Variations: looping producer ê Producer ê Infinite loop ok? ê Why/why not? producer () { lock (soda. Lock) while (1) { while(num. Sodas==Max. Sodas){ wait (soda. Lock, has. Room) } ê Release lock in wait call add soda to machine signal (has. Soda) } unlock (soda. Lock) }
Variations: resting producer ê Producer ê Sleep ok? ê Why/why not? ê Shouldn’t hold locks during a slow operation producer () { lock (soda. Lock) while (1) { sleep (1 hour) while(num. Sodas==Max. Sodas){ wait (soda. Lock, has. Room) } add soda to machine signal (has. Soda) } unlock (soda. Lock) }
Variations: one CV? consumer () { lock (soda. Lock) producer () { lock (soda. Lock) while (num. Sodas == 0) { wait (soda. Lock, has. Ror. S) } take soda out of machine add soda to machine signal (has. Ror. S) unlock (soda. Lock) } while(num. Sodas==Max. Sodas){ wait (soda. Lock, has. Ror. S) } unlock (soda. Lock) } Two producers, two consumers: who consumes a signal? Producer. A and Consumer. B wait while Consumer. C signals?
Variations: one CV? consumer () { lock (soda. Lock) producer () { lock (soda. Lock) while (num. Sodas == 0) { wait (soda. Lock, has. Ror. S) } take soda out of machine add soda to machine signal (has. Ror. S) unlock (soda. Lock) } while(num. Sodas==Max. Sodas){ wait (soda. Lock, has. Ror. S) } unlock (soda. Lock) } Is it possible to have a producer and consumer both waiting? max=1, c. A and c. B wait, p. C adds/signals, p. D waits, c. A wakes
Variations: one CV? consumer () { lock (soda. Lock) producer () { lock (soda. Lock) while (num. Sodas == 0) { wait (soda. Lock, has. Ror. S) } take soda out of machine add soda to machine signal (has. Ror. S) unlock (soda. Lock) } while(num. Sodas==Max. Sodas){ wait (soda. Lock, has. Ror. S) } unlock (soda. Lock) } How can we make the one CV solution work?
Variations: one CV? consumer () { lock (soda. Lock) producer () { lock (soda. Lock) while (num. Sodas == 0) { wait (soda. Lock, has. Ror. S) } take soda out of machine add soda to machine broadcast (has. Ror. S) unlock (soda. Lock) } while(num. Sodas==Max. Sodas){ wait (soda. Lock, has. Ror. S) } unlock (soda. Lock) } Use broadcast instead of signal
Broadcast vs signal ê Can I always use broadcast instead of signal? ê Yes, assuming threads recheck condition ê Why might I use signal instead? ê Efficiency (spurious wakeups) ê May wakeup threads for no good reason ê Next class: reader/writer locks


