Monitor (Hoare 1974) • Idea by Brinch-Hansen 1973 in the textbook “Operating Monitors System Principles” Condition Variables • Structure an OS into a set of modules each implementing a resource scheduler • Tony Hoare – Combine together in each module Otto J. Anshus – Mutex University of {Tromsø, Oslo} – Shared data – Access methods to shared data – Condition synchronization – Local code and data The Structure of a Monitor MUTEX •After calling, threads get So only ONE blocked and are waiting Signal and Wait monitor The Monitor to get in and start procedure executing the called executes at a Main Queue monitor procedure time • Wait (cond) • Signal (cond) •Threads waiting on a condition Condition Queue 1 variable for a condition to be – Insert(caller, cond_queue) – Stop monitor procedure Condition Queue n true (waiting for a signal on the calling signal – Block this instance of the Threads calling a condition variable) <More to come> monitor procedure – Start first in cond_queue, or monitor Signal (): {…} Wait (): {…} System implementation procedure just return if empty – open MUTEX by getting Local variables User implementation Shared variables next call from Main_Queue Local procedure 1 Local procedure m •The only way to access shared Monitor procedure 1: {…wait(condvar); …} resources is by calling a monitor procedure Monitor procedure k: {… signal(condvar); …} •Initialization of state Initialization executed first time the monitor starts variables, executed ONCE at startup of monitor /*Local functions, variables*/ Single Resource Monitor <none needed> Implementation of the Monitor Concept /*Shared variable*/ Boolean busy ; /*Condition variable*/ Condition nonbusy ; All threads must follow the pattern: • As a primitive in a language (Mesa, Java) Reserve : • Using semaphores in any language Reserve; { • As a thread or as a process <use shared resource> if (busy) wait (nonbusy); – Need a way to interact with the thread busy:=TRUE; Release; – through shared variables to deliver the parameters and name of } called monitor procedure – Need a way to interact with the process – kernel support of shared variables across address spaces Release : – using another mechanism like message passing to pass { parameters and name of procedure Observe busy:=FALSE; • At user level, use condition variables (the queues), wait(), signal() signal (nonbusy); •the shared variable implementd by } •the naming of the condition variable – the operating system kernel – a thread package (Pthreads) •the wait and signal calls /* Initialization code*/ •implements a binary semaphore (s=0,1) busy:=FALSE; nonbusy:=EMPTY;
What is a Condition Variable? Semaphore vs. Monitor The Monitor Semaphore Monitor • No “value” Main Queue • Waiting queue Condition Queue 1 Wait (cond) means unconditional WAIT P (s) means WAIT if s=0 Condition Queue n • Used to represent a condition And s-- <More to come> we need to wait for to be Signal (): {…} Wait (): {…} Local variables Shared variables TRUE V (s) means start a waiting Signal (cond) means start a Local procedure 1 thread and REMEMBER that a waiting thread. But no memory! • Initial “non-value” is EMPTY Local procedure m V call was made: s++ Assume that the condition :-) Monitor procedure 1: {…wait(condvar); …} Assume s=0 when V(s) is queue is empty when signal() is called: If there is no thread to called. The next thread to call start this time, the next thread to Wait(cond) (by executing a Monitor procedure 1: {… signal(condvar); …} call P(s) will get through P(s) monitor procedure!) will block because the signal() operation Initialization executed first time the monitor starts did not leave any trace of the fact that it was executed on an empty condition waiting queue. Bounded Buffer Monitor /*Local functions, variables*/ int in, out; What will happen when a signal() is out /*Shared variable*/ int B(0..n-1), count ; B executed? /*Condition variable*/ in Condition nonfull, nonempty ; Capacity: N Put (int m) : • Assume we have threads in Main_Queue and in a { if (count=n) wait (nonfull); r:=GET: condition queue PUT (m): B(in):=m; /* MOD is % */ in:=in+1 MOD n; • Main_Queue has lower “priority” than the signaled Producer Consumer count++; condition queue: signal (nonempty); } Rules for the buffer B: One condition variable • signal() => Take first from condition queue one and start it for each condition: from its next instruction after the wait() which blocked it •No Get when empty int Get: •nonempty { if (count=0) wait (nonempty); • The signaled thread now executes •No Put when full Get:=B(out); •nonfull – … until a wait(): block it, and take new from Main_Queue •B shared, so must have out:=out+1 MOD n; mutex between Put and •MUTEX is already count--; – … until a signal(): Get provided by the monitor signal (nonfull); } – … until finished: take new from Main_Queue /* Initialization code*/ in:=out:=count:=0; nonfull, nonempty:=EMPTY; Where to allow a call • Look at the two monitors we have to signal()? analyzed! Where is the signal() Options of the Signaler operation? • What if we called signal somewhere else? The Monitor • Relinquishes control to the awaken process Main Queue • The calling function instance must be – Complex if the signaler has other work to to blocked, awaiting return from Condition Queue 1 signal() – To make sure there is no work to do is difficult because the Condition Queue n signal implementation is not aware how it is used URGENT Queue – Need a queue for the temporary Signal (): {…} Wait (): {…} halted thread – It is easy to prove things Local variables Shared variables • URGENT QUEUE Local procedure 1 • Continues its execution Local procedure m • In Hoare’s monitors the signal – Easy to implement Monitor procedure 1: {…wait(condvar); …} operation must IMMEDIATELY start – But, the condition may not be true when the awaken process the signaled thread in order for the actually gets a chance to run condition that it signals about still to Monitor procedure 1: {… signal(condvar); …} Initialization executed first time the monitor starts be guaranteed true when the thread starts
Performance problems of Monitors? • Getting in through Main_Queue Mutex between monitor procedures? • Many can be in Main_Queue and in a condition queue waiting for a thread to execute a monitor procedure calling a signal. – Can take a long time before the signaler gets in • Hoare: Yes • Need one Wait_Main_Queue and one Signal_Main_Queue? • But not needed if we have no shared variables – But difficult when all procedures call both wait and signal – But signal and wait must be atomic because they can access • The monitor is a potential bottleneck (“Bottleneck OS”??) the same condition variable – Use several to avoid hot spots • So no gain? • Signal must start the signaled thread immediately, so must – Finer granularity (is good) switch thread context and save our own – Makes life harder (is bad) • Can have nested calls • Should be possible to Put and Get at each end of a buffer? • Even worse for process context switches – Try it – Solution? • Avoid starting the signaled thread immediately • But then race conditions can happen Bounded Buffer Mesa /*Local functions, variables*/ int in, out, count; Monitors /*Shared variable*/ int B(0..n-1) ; Mesa Style “Monitor” (Birrell’s Paper) /* Mutex */ Wait will mutex_t bb_mutex ; UNLOCK /*Condition variable*/ out Condition nonfull, nonempty ; B Put (int m) : • Wait( lock, condition ) LOCK bb_mutex { in Spins to { while (count=n) wait (bb_mutex, nonfull); – Atomically unlock the mutex and enqueue on the condition B(in):=m; reevaluate Capacity: N variable (block the thread) in:=in+1 MOD n; count++; r:=GET (r): – Re-lock the lock when it is awaken signal (nonempty); } PUT (m): } • Signal( condition ) Is really a NOTIFY or a HINT Producer Consumer int Get: – Noop if there is no thread blocked on the condition variable LOCK bb_mutex { { while (count=0) wait (bb_mutex, nonempty); – Wake up at some convenient time at least one (if there are Rules for the buffer B: One condition for each Get:=B(out); out:=out+1 MOD n; threads blocked) condition: •No Get when empty count--; •nonempty signal (nonfull); } • Broadcast( condition ) •No Put when full } •nonfull – Wake up all threads waiting on the condition •B shared, so must have /* Initialization code*/ mutex between Put and •MUTEX is locked by in:=out:=count:=0; Get LOCK and unlocked by nonfull, nonempty:=EMPTY; Wait Implementing Semaphores with Mesa- Monitors P(mutex); while () wait(cvar) P( s ) V( s ) ………………. { { V(mutex) Acquire( s.mutex ); Acquire( s.mutex ); --s.value; ++s.value; if (s.value < 0 ) if (s.value >= 0 ) wait ( s.mutex, s.cond ); signal ( s.cond ); Release( s.mutex); Release( s.mutex); } } Assume that Signal wakes up exactly one awaiting thread.
Recommend
More recommend