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 , the “real” abstraction is a

● 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 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 , 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 – 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