Synchronization: Semaphores, Monitors, and Condition Variables
Total Page:16
File Type:pdf, Size:1020Kb
Today’s Topics u Mutex Isn’t Enough COS 318: Operating Systems u Semaphores u Condition Variables Synchronization: Semaphores, u Monitors Monitors and Condition Variables u Barriers Jaswinder Pal Singh and a Fabulous Course Staff Computer Science Department Princeton University (http://www.cs.princeton.edu/courses/cos318/) 2 Revisit Mutex Producer-Consumer (Bounded Buffer) Problem u Mutex can solve the critical section problem Acquire( lock ); Producer: Consumer: while (1) { Critical section while (1) { produce an item remove an item from buffer Release( lock ); Insert item in buffer count--; u Use Mutex primitives to access shared data structures count++; consume an item E.g. shared “count” variable } } Acquire( lock ); count = 4 count++; Release( lock ); N = 12 u Are mutex primitives adequate to solve all problems? u Can we solve this problem with Mutex primitives? 3 4 1 Use Mutex, Block and Unblock Use Mutex, Block and Unblock Producer: Consumer: Producer: Consumer: while (1) { while (1) { while (1) { while (1) { produce an item if (!count) produce an item if (!count) if (count == N) Block(); if (count == N) {context switch} Block(); remove an item from buffer Block(); Block(); Insert item in buffer Acquire(lock); Insert item in buffer remove an item from buffer Acquire(lock); count--; Acquire(lock); Acquire(lock); count++; Release(lock); count++; count--; Release(lock); if (count == N-1) Release(lock); Release(lock); if (count == 1) count = 4 Unblock(Producer); if (count == 1) countcountcount = == 12 01 if (count == N-1) Unblock(Consumer); consume an item Unblock(Consumer); Unblock(Producer); } N = 12 } } N = 12 consume an item } u Does this work? u Race condition! u Ultimately, both block and never wake up u Lost the unblock; any way to “remember” them? 5 6 Limitations of Locks Semaphores (Dijkstra, 1965) u A semaphore is a synchronization variable that contains an u Provide mutual exclusion: only one process/thread can be in the integer value critical section at a time l Cannot access the integer value directly (only via semaphore operations) u Do not provide ordering or sequencing (aka event synchronization) l Initialized to some integer value l Who gets to be in critical section first? l Supports two atomic operations other than initialization l How does thread A wait for thread B (or C, D, E) to do X before A does Y? • down() (or wait() or P()) u Need additional synchronization mechanisms • up (or signal() or V()) l Semaphores u If positive value, think of value as keeping track of how many ‘resources’ or “un-activated unblocks” are available l Condition Variables u If negative, tracks how many threads are waiting for a resource l Monitors or unblock l (Higher level constructs composed from these) 7 8 2 Semaphores (Dijkstra, 1965) Bounded Buffer with Semaphores “ ” u P (or Down or Wait or Proberen (to try)) definition Producer: Consumer: l Atomic operation while (1) { while (1) { l Block version: Decrement value, and if result less than zero then block produce an item P(fullCount); l Spin version: Wait for semaphore to become positive and then decrement P(emptyCount); P(s){ P(s){ P(mutex); if (--s < 0) while (s <= 0) P(mutex); take an item from buffer block(s); ; put item in buffer V(mutex); s--; V(mutex); } } V(emptyCount); V(fullCount); consume item u V (or Up or Signal or “Verhogen” (increment)) definition } } l Atomic operation l Block version: increment, and if non-positive (which means at least one thread is blocked waiting on the sempahore) then unblock a thread u Initialization: emptyCount = N; fullCount = 0 l Spin version: Increment semaphore u Are P(mutex)and V(mutex) necessary? V(s){ V(s){ if (++s <=0) s++; unblock(s); } } 9 Uses of Semaphores in this Example Example: Interrupt Handler u Event sequencing l Don’t consume if buffer empty, wait for something to be Init(s,0); added l Don’t add if buffer full, wait for something to be removed Device thread Interrupted Thread u Mutual exclusion while (1) { P(s); … l Avoid race conditions on shared variables Acquire(m); Interrupt handler ... ... Interrupt deal with interrupt V(s); … ... ... Release(m); } 11 12 3 Bounded Buffer with Semaphores (again) Does Order Matter? producer() { consumer() { producer() { consumer() { while (1) { while (1) { while (1) { while (1) { produce an item P(fullCount); produce an item P(fullCount); P(emptyCount); P(mutex); P(mutex); P(emptyCount); P(mutex); P(mutex); take an item from buffer take an item from buffer put the item in buffer V(mutex); put the item in buffer V(mutex); V(mutex); V(mutex); V(emptyCount); V(emptyCount); V(fullCount); consume the item V(fullCount); consume the item } } } } } } } } Another Example: Are Locks Enough? Condition Variables u Make tryRemove wait until something is on the queue? u A lock provides mutual exclusion to the shared data l Can’t just sleep while holding the lock u Rules for using a lock: l Key idea: make it possible to go to sleep inside critical section, l Always acquire before accessing shared data structure by atomically releasing lock at same time we go to sleep. l Always release after finishing with shared data l Lock is initially free. u Condition variable: enables a queue of threads waiting for something inside a critical section. u Simple example: a synchronized queue l Wait() --- Release lock, go to sleep, re-acquire when woken • release lock and going to sleep is atomic bool tryRemove() bool tryInsert() { … { lock.Acquire(); l Signal() --- Wake up a waiter, if any lock.Acquire(); // lock before use if something on queue // can we wait? l Broadcast() --- Wake up all waiters … put item on queue; // ok to access remove it; lock.Release(); // unlock after done lock->Release(); return success; return success; } } 4 Synchronized Queue Condition variable design pattern u Rule: must hold lock when doing condition variable methodThatWaits() { methodThatSignals() { operations lock.acquire(); lock.acquire(); RemoveFromQueue() // Read/write shared state // Read/write shared state AddToQueue() { { lock.acquire(); lock.acquire(); while (!testSharedState()) { // If testSharedState is now true while nothing on queue cv.wait(&lock); cv.signal(&lock); put item on queue; condition.wait(&lock); } condition.signal(); // release lock; got to // sleep; reacquire lock // Read/write shared state // Read/write shared state lock.release(); // when woken } lock.release(); remove item from queue; lock.release(); } lock.release(); } return item; } Condition variables Structured synchronization u ALWAYS hold lock when calling wait, signal, broadcast u Identify objects or data structures that can be accessed l Condition variable is synchronization FOR shared state by multiple threads concurrently l Remember: ALWAYS hold lock when accessing shared state u Add locks to object/module l Obtain lock on start to every method/procedure u Unlike semaphore, condition variable is memory-less l Release lock when finished l If signal when no one is waiting, no op u If need to wait l If signal after a wait is posted, a waiter wakes up l while(needToWait()) { condition.Wait(lock); } u If do something that should wake someone up u Wait atomically releases lock l Signal or Broadcast u Always leave shared state variables in a consistent state l When lock is released, or when waiting 5 Monitors Monitors Embedded in Languages u High-level data abstraction that unifies handling of: u Monitor definition: l Shared data, operations on it, synchronization and scheduling l a lock and zero or more condition variables for managing • All operations on data structure have single (implicit) lock concurrent access to shared data • An operation can relinquish control and wait on condition // only one process at time can update instance of Q u Monitors make things easier: class Q { int head, tail; // shared data l “locks” for mutual exclusion void enqueue(v) { locked access to Q instance } l “condition variables” for scheduling constraints int dequeue() { locked access to Q instance } } l Java from Sun; Mesa/Cedar from Xerox PARC u Monitors are easy and safe l Compiler can check, lock is implicit (cannot be forgotten) Monitor: Hide Mutual Exclusion Condition Variables in A Monitor u Brinch-Hansen (73), Hoare (74) u Wait(condition) Queues associated u Procedures are mutually exclusive l Block on “condition” with x, y u Signal(condition) conditions x Shared l Wakeup a blocked process y data on “condition” Queue of waiting processes Shared trying to enter the monitor data ... procedures ... Entry queue procedures 6 Producer-Consumer with Monitors Hoare’s Signal Implementation (MOS p137) monitor ProdCons procedure Producer monitor ProdCons u Run the signaled thread condition full, empty; condition full, empty; begin immediately and suspend while true do procedure Enter; begin procedure Enter; the current one (Hoare) begin produce an item begin if (buffer is full) ProdCons.Enter(); if (buffer is full) wait(full); wait(full); end; u What if the current thread end; put item into buffer; put item into buffer; if (only one item) has more things to do? if (only one item) signal(empty); procedure Consumer signal(empty); end; end; begin if (only one item) while true do signal(empty); procedure Remove; begin procedure Remove; something else begin ProdCons.Remove(); begin end; consume an item; if (buffer is empty) if (buffer is empty) wait(empty); end; wait(empty); end; remove an item; remove an item; if (buffer was full) if (buffer was full) signal(full); signal(full); end; end; 31 Hansen’s Signal Implementation (MOS p 137) Mesa Signal Implementation u Signal must be the last monitor ProdCons u Continues its execution statement of a monitor condition full, empty; if (only one item) signal(empty); procedure procedure Enter; something else begin u end; Exit the monitor if (buffer is full) wait(full); put item into buffer; l B. W. Lampson and D. D. Redell, “Experience with Processes and u Any issue with this if (only one item) Monitors in Mesa,” Communiction of the ACM, 23(2):105-117.