Synchronization Monitors
Total Page:16
File Type:pdf, Size:1020Kb
CS 4410 Operating Systems Synchronization Monitors Summer 2013 Cornell University 1 Today ● What are the monitors and how can we use them? ● Monitor Semantics/Structure ● Solving synchronization problems with monitors ● Comparison with semaphores ● Mapping to Real Languages 2 Semaphors: Common programming errors Process i Process j Process k Process m P(S) V(S) P(S) P(S) CS CS CS if(something or other) P(S) V(S) return; CS V(S) 3 Revisiting semaphores! ● Semaphores are very “low-level” primitives ● Users could easily make small errors ● Similar to programming in assembly language ● Small error brings system to grinding halt ● Very difficult to debug ● Also, we seem to be using them in two ways ● For mutual exclusion, the “real” abstraction is a critical section ● But the bounded buffer example illustrates something different, where threads “communicate” using semaphores ● Simplification: Provide concurrency support in compiler ● Monitors 4 Monitors ● Hoare 1974 ● Abstract Data Type for handling/defining shared resources ● Comprises: ● Shared Private Data – The resource – Cannot be accessed from outside ● Procedures that operate on the data – Gateway to the resource – Can only act on data local to the monitor ● Synchronization primitives – Among threads that access the procedures 5 Monitor Semantics ● Monitors guarantee mutual exclusion ● Only one thread can execute a monitor procedure at any time. – “in the monitor” ● If second thread invokes a monitor procedure at that time – It will block and wait for entry to the monitor – Need for a wait queue 6 Structure of a Monitor Monitor monitor_name For example: { // shared variable declarations Monitor stack { procedure P1(. .) { int top; . void push(any_t *) { } . } procedure P2(. .) { . any_t * pop() { } . } procedure PN(. .) { . initialization_code() { } . } initialization_code(. .) { } . } 7 } Structure of a Monitor shared data ... entry queue operations initialization code 8 Synchronization Using Monitors ● Defines Condition Variables: ● condition x; ● Provides a mechanism to wait for events. ● 3 atomic operations on Condition Variables ● x.wait(): release monitor lock, sleep until woken up – Condition variables have a waiting queue ● x.notify(): wake one process waiting on condition (if there is one) – No history associated with signal – Notify and wait – Notify and continue ● x.notifyAll(): wake all processes waiting on condition – Useful for resource manager 9 Synchronization Using Monitors shared data x y ... entry queue operations initialization code 10 Types of wait queues ● Monitors have two kinds of “wait” queues ● Entry to the monitor: has a queue of threads waiting to obtain mutual exclusion so they can enter ● Condition variables: each condition variable has a queue of threads waiting on the associated condition 11 A Simple Monitor Monitor EventTracker { int numburgers = 0; condition hungrycustomer; void customerenter() { if (numburgers == 0) hungrycustomer.wait() numburgers -= 1 } void produceburger() { ++numburgers; hungrycustomer.signal(); } } 12 A Simple Monitor ● Because condition variables Monitor EventTracker { lack state, all state must be int numburgers = 0; kept in the monitor condition hungrycustomer; ● The condition for which the void customerenter() { threads are waiting is if (numburgers == 0) necessarily made explicit in hungrycustomer.wait() the code numburgers -= 1 } ● Numburgers > 0 void produceburger() { ++numburgers; ● What happens if there are lots of hungrycustomer.signal(); customers? } } ● Signal and wait vs. Signal and continue semantics 13 A Simple Monitor ● Because condition variables Monitor EventTracker { lack state, all state must be int numburgers = 0; kept in the monitor condition hungrycustomer; ● The condition for which the void customerenter() { threads are waiting is while (numburgers == 0) necessarily made explicit in hungrycustomer.wait() the code numburgers -= 1 } ● Numburgers > 0 void produceburger() { ++numburgers; ● What happens if there are lots of hungrycustomer.signal(); customers? } } ● Signal and wait vs. Signal and continue semantics 14 Producer-Consumer using Monitors Monitor Producer_Consumer { any_t buf[N]; int n = 0, tail = 0, head=0; condition not_empty, not_full; void put(char ch) { char get() { if(n == N) if(n == 0) wait(not_full); wait(not_empty); buf[head%N] = ch; ch = buf[tail%N]; head++; tail++; n++; n--; signal(not_empty); signal(not_full); } return ch; } } 15 Readers and Writers Monitor ReadersNWriters { Void BeginRead(){ int WaitingWriters, WaitingReaders, if(NWriters == 1 || WaitingWriters > 0) NReaders, Nwriters; { Condition CanRead, CanWrite; ++WaitingReaders; Void BeginWrite() { if(NWriters == 1 || NReaders > 0) Wait(CanRead); { ++WaitingWriters; --WaitingReaders; wait(CanWrite); --WaitingWriters; } } ++NReaders; NWriters = 1; } Signal(CanRead);} Void EndWrite() Void EndRead(){ { NWriters = 0; if(--NReaders == 0) if(WaitingReaders) Signal(CanRead); Signal(CanWrite);} else } Signal(CanWrite); } 16 Readers and Writers Monitor ReadersNWriters { Void BeginRead(){ int WaitingWriters, WaitingReaders, if(NWriters == 1 || WaitingWriters > 0) NReaders, NWriters; { Condition CanRead, CanWrite; ++WaitingReaders; Void BeginWrite() { Wait(CanRead); if(NWriters == 1 || NReaders > 0) { --WaitingReaders; ++WaitingWriters; wait(CanWrite); } --WaitingWriters; ++NReaders; } NWriters = 1; Signal(CanRead);} } Void EndWrite() { Void EndRead() { NWriters = 0; if(WaitingReaders) if(--NReaders == 0) Signal(CanRead); Signal(CanWrite); } else Signal(CanWrite); } } 17 Readers and Writers Monitor ReadersNWriters { Void BeginRead() { int WaitingWriters, WaitingReaders, if(NWriters == 1 || WaitingWriters > 0) NReaders, NWriters; { Condition CanRead, CanWrite; ++WaitingReaders; Void BeginWrite(){ if(NWriters == 1 || NReaders > 0) Wait(CanRead); { ++WaitingWriters; --WaitingReaders; wait(CanWrite); --WaitingWriters; } } ++NReaders; NWriters = 1;} Signal(CanRead);} Void EndWrite() { NWriters = 0; if(WaitingReaders) Signal(CanRead); Void EndRead(){ else Signal(CanWrite); } if(--NReaders == 0) Signal(CanWrite); } } 18 Readers and Writers Monitor ReadersNWriters { Void BeginRead(){ int WaitingWriters, if(NWriters == 1 || WaitingWriters > 0) WaitingReaders,NReaders, NWriters; { Condition CanRead, CanWrite; ++WaitingReaders; Void BeginWrite(){ if(NWriters == 1 || NReaders > 0) Wait(CanRead); { ++WaitingWriters; --WaitingReaders; wait(CanWrite); --WaitingWriters; } } ++NReaders; NWriters = 1; } Signal(CanRead);} Void EndWrite(){ NWriters = 0; if(WaitingReaders) Signal(CanRead); Void EndRead(){ else Signal(CanWrite);} if(--NReaders == 0) Signal(CanWrite); } } 19 Understanding the Solution ● A reader can enter if ● There are no writers active or waiting ● When a writer finishes, it checks to see if any readers are waiting ● If so, it lets one of them enter ● That one will let the next one enter, etc… ● Similarly, when a reader finishes, if it was the last reader, it lets a writer in (if any is there) ● Do not forget: For Signal and Continue semantics we have 'while' instead of 'if' at BeginWrite and BeginRead! 20 Understanding the Solution ● It wants to be fair ● If a writer is waiting, readers queue up ● If a reader (or another writer) is active or waiting, writers queue up ● … this is mostly fair, although once it lets a reader in, it lets ALL waiting readers in all at once, even if some showed up “after” other waiting writers 21 Mapping to Real Languages Monitor ReadersNWriters { int x; Class ReadersNWriters: Condition foo def __init__(self): self.lock = Lock() self.foo = Condition(self.lock) Void func() { def func(): while(x == 0) with self.lock: while x == 0: { self.foo.wait() foo.wait() x = 1 } x = 1 } } 22 Condition Variables & Semaphores ● Condition Variables != semaphores ● Access to monitor is controlled by a lock ● Wait: blocks thread and gives up the monitor lock – To call wait, thread has to be in monitor, hence the lock – Semaphore P() blocks thread only if value less than 0 ● Signal: causes waiting thread to wake up – If there is no waiting thread, the signal is lost – V() increments value, so future threads need not wait on P() – Condition variables have no history! However they can be used to implement each other 23 Today ● What are the monitors and how can we use them? ● Monitor Semantics/Structure ● Solving synchronization problems with monitors ● Comparison with semaphores ● Mapping to Real Languages 24.