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