1 / 31

Monitor Solutions to Classical Problems

Monitor Solutions to Classical Problems. Announcements. CS 415 Projects graded. Mean 80.7, High 90 out of 90 CS 414 Homework due Monday. Goals for Today. Continue with Synchronization Abstractions Monitors and condition variables Producer Consumer with Monitors

hani
Download Presentation

Monitor Solutions to Classical Problems

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Monitor Solutions to Classical Problems

  2. Announcements • CS 415 Projects graded. • Mean 80.7, High 90 out of 90 • CS 414 Homework due Monday.

  3. Goals for Today • Continue with Synchronization Abstractions • Monitors and condition variables • Producer Consumer with Monitors • Readers-Writers problem and solution with Monitors

  4. Check and/or updatestate variables Wait if necessary Check and/or update state variables Review: Monitors • Monitors represent the logic of the program • Wait if necessary • Signal when change something so any waiting threads can proceed • Basic structure of monitor-based program: lock while (need to wait) {unlock condvar.wait(); lock}do something so no need to wait condvar.signal(); unlock

  5. Review: Producer-consumer with a bounded buffer • Problem Definition • Producer puts things into a shared buffer (wait if full) • Consumer takes them out (wait if empty) • Use a fixed-size buffer between them to avoid lockstep • Need to synchronize access to this buffer • Correctness Constraints: • Consumer must wait for producer to fill buffers, if none full • scheduling constraint • Producer must wait for consumer to empty buffers, if all full • scheduling constraint • Only one thread can manipulate buffer queue at a time • mutual exclusion • Remember why we need mutual exclusion • Because computers are stupid • General rule of thumb:Use a separate semaphore for each constraint • Semaphore not_empty; // consumer’s constraint • Semaphore not_full; // producer’s constraint • Semaphore mutex; // mutual exclusion

  6. Producer-Consumer using Semaphores Init: Semaphore mutex = 1; /* for mutual exclusion*/ Semaphore not_full = N; /* number empty buf entries */ Semaphore not_empty = 0; /* number full buf entries */ any_t buf[N]; int tail = 0, head = 0; Producer void put(char ch) { P(not_full); P(mutex); // add ch to buffer buf[head%N] = ch; head++; V(mutex); V(not_empty); } Consumer char get() { P(not_empty); P(mutex); // remove ch from buffer ch = buf[tail%N]; tail++; V(mutex); V(not_full); return ch; }

  7. Discussion about Bounded Buffer Solution • Why asymmetry? • Producer does: P(not_full), V(not_empty) • Consumer does: P(not_empty), V(not_full) • Is order of P’s important? • Yes! Can cause deadlock • Is order of V’s important? • No, except that it might affect scheduling efficiency • What if we have 2 producers or 2 consumers? • Do we need to change anything?

  8. Review: Motivation for Monitors and Condition Variables • Semaphores are a huge step up, but: • They are confusing because they are dual purpose: • Both mutual exclusion and scheduling constraints • Example: the fact that flipping of P’s in bounded buffer gives deadlock is not immediately obvious • Cleaner idea: Use locks for mutual exclusion and condition variablesfor scheduling constraints • Definition: Monitor: a lock and zero or more condition variables for managing concurrent access to shared data • Use of Monitors is a programming paradigm • Some languages like Java provide monitors in the language • The lock provides mutual exclusion to shared data: • Always acquire before accessing shared data structure • Always release after finishing with shared data • Lock initially free

  9. Condition Variables • How do we change the get() routine to wait until something is in buffer? • Could do this by keeping a count of the number of things on the queue (with semaphores), but error prone • Condition Variable: a queue of threads waiting for something inside a critical section • Key idea: allow sleeping inside critical section by atomically releasing lock at time we go to sleep • Contrast to semaphores: Can’t wait inside critical section • Operations: • Wait(&lock): Atomically release lock and go to sleep. Re-acquire lock later, before returning. • Signal(): Wake up one waiter, if any • Broadcast(): Wake up all waiters • Rule: Must hold lock when doing condition variable ops!

  10. 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) { while(n == N) wait(not_full); buf[head%N] = ch; head++; n++; signal(not_empty); } char get() { while(n == 0) wait(not_empty); ch = buf[tail%N]; tail++; n--; signal(not_full); return ch; }

  11. Reminders: Subtle aspects • Notice that when a thread calls wait(), if it blocks it also automatically releases the monitor’s mutual exclusion lock • This is an elegant solution to an issue seen with semaphores • Caller has mutual exclusion and wants to call P(not_empty)… but this call might block • If we just do the call, the solution deadlocks… • But if we first call V(mutex), we get a race condition!

  12. Review: Mesa vs. Hoare monitors • Need to be careful about precise definition of signal and wait. Consider a piece of our dequeue code: while (n==0) { wait(not_empty); // If nothing, sleep } ch = buf[tail%N]; // Get next item • Why didn’t we do this? if (n==0) { wait(not_empty); // If nothing, sleep } ch = buf[tail%N]; // Get next item • Answer: depends on the type of scheduling • Hoare-style (most textbooks): • Signaler gives lock, CPU to waiter; waiter runs immediately • Waiter gives up lock, processor back to signaler when it exits critical section or if it waits again • Mesa-style (Java, most real operating systems): • Signaler keeps lock and processor • Waiter placed on ready queue with no special priority • Practically, need to check condition again after wait

  13. Review: Can we construct Monitors from Semaphores? • Locking aspect is easy: Just use a mutex • Can we implement condition variables this way? Wait() { P(x_sem); } Signal() { V(x_sem); } • Doesn’t work: Wait() may sleep with lock held • Does this work better? Wait() { V(mutex); // Release mutex lock P(x_sem); P(mutex); // Acquire mutex lock}Signal() { V(x_sem); } • No: Condition vars have no history, semaphores have history: • What if thread signals and no one is waiting? NO-OP • What if thread later waits? Thread Waits • What if thread V’s and noone is waiting? Increment • What if thread later does P? Decrement and continue

  14. Construction of Monitors from Semaphores (con’t) • Problem with previous try: • P and V are commutative – result is the same no matter what order they occur • Condition variables are NOT commutative • Does this fix the problem? Wait(Lock lock) { V(mutex); // Release mutex lock P(x_sem); P(mutex); // Acquire mutex lock}Signal() { if semaphore queue is not empty V(x_sem);} • Not legal to look at contents of semaphore queue • There is a race condition – signaler can slip in after lock release and before waiter executes semaphore.P() • It is actually possible to do this correctly • Complex solution for Hoare scheduling in book • Can you come up with simpler Mesa-scheduled solution?

  15. Construction of Hoare Monitors using Semaphores Wait(){ x_count++; if(next_count > 0) V(next); else V(mutex); P(x_sem); x_count--; For each procedure F: P(mutex); /* body of F */ if(next_count > 0) V(next); else V(mutex); Signal(){ If(x_count > 0) { next_count++; V(x_sem); P(next); next_count--; }

  16. W R R R Revisits: Readers/Writers Problem • Motivation: Consider a shared database • Two classes of users: • Readers – never modify database • Writers – read and modify database • Is using a single lock on the whole database sufficient? • Like to have many readers at the same time • Only one writer at a time

  17. Revisit: Readers/Writers Problem • Correctness Constraints: • Readers can access database when no writers • Writers can access database when no readers or writers • Only one thread manipulates state variables at a time • Basic structure of a solution: • Reader() Wait until no writers Access data base Check out – wake up a waiting writer • Writer() Wait until no active readers or writers Access database Check out – wake up waiting readers or writer • State variables (Protected by a lock called “lock”): • int NReaders: Number of active readers; initially = 0 • int WaitingReaders: Number of waiting readers; initially = 0 • int NWriters: Number of active writers; initially = 0 • int WaitingWriters: Number of waiting writers; initially = 0 • Condition canRead = NIL • Conditioin canWrite = NIL

  18. Revisit: Readers-Writers Problem • One issue we need to settle, to clarify problem statement. • Suppose that a writer is active and a mixture of readers and writers now shows up. Who should get in next? • Or suppose that a writer is waiting and an endless of stream of readers keeps showing up. Is it fair for them to become active? • We’ll favor a kind of back-and-forth form of fairness: • Once a reader is waiting, readers will get in next. • If a writer is waiting, one writer will get in next.

  19. Readers and Writers Monitor ReadersNWriters { int WaitingWriters, WaitingReaders,NReaders, NWriters; Condition CanRead, CanWrite; Void BeginWrite() { if(NWriters == 1 || NReaders > 0) { ++WaitingWriters; wait(CanWrite); --WaitingWriters; } NWriters = 1; } Void EndWrite() { NWriters = 0; if(WaitingReaders) Signal(CanRead); else Signal(CanWrite); } Void BeginRead() { if(NWriters == 1 || WaitingWriters > 0) { ++WaitingReaders; Wait(CanRead); --WaitingReaders; } ++NReaders; Signal(CanRead); } Void EndRead() { if(--NReaders == 0) Signal(CanWrite); }

  20. Readers and Writers Monitor ReadersNWriters { int WaitingWriters, WaitingReaders,NReaders, NWriters; Condition CanRead, CanWrite; Void BeginWrite() { if(NWriters == 1 || NReaders > 0) { ++WaitingWriters; wait(CanWrite); --WaitingWriters; } NWriters = 1; } Void EndWrite() { NWriters = 0; if(WaitingReaders) Signal(CanRead); else Signal(CanWrite); } Void BeginRead() { if(NWriters == 1 || WaitingWriters > 0) { ++WaitingReaders; Wait(CanRead); --WaitingReaders; } ++NReaders; Signal(CanRead); } Void EndRead() { if(--NReaders == 0) Signal(CanWrite); }

  21. Readers and Writers Monitor ReadersNWriters { int WaitingWriters, WaitingReaders,NReaders, NWriters; Condition CanRead, CanWrite; Void BeginWrite() { if(NWriters == 1 || NReaders > 0) { ++WaitingWriters; wait(CanWrite); --WaitingWriters; } NWriters = 1; } Void EndWrite() { NWriters = 0; if(WaitingReaders) Signal(CanRead); else Signal(CanWrite); } Void BeginRead() { if(NWriters == 1 || WaitingWriters > 0) { ++WaitingReaders; Wait(CanRead); --WaitingReaders; } ++NReaders; Signal(CanRead); } Void EndRead() { if(--NReaders == 0) Signal(CanWrite); }

  22. Readers and Writers Monitor ReadersNWriters { int WaitingWriters, WaitingReaders,NReaders, NWriters; Condition CanRead, CanWrite; Void BeginWrite() { if(NWriters == 1 || NReaders > 0) { ++WaitingWriters; wait(CanWrite); --WaitingWriters; } NWriters = 1; } Void EndWrite() { NWriters = 0; if(WaitingReaders) Signal(CanRead); else Signal(CanWrite); } Void BeginRead() { if(NWriters == 1 || WaitingWriters > 0) { ++WaitingReaders; Wait(CanRead); --WaitingReaders; } ++NReaders; Signal(CanRead); } Void EndRead() { if(--NReaders == 0) Signal(CanWrite); }

  23. Understanding the Solution • A writer can enter if there are no other active writers and no readers are waiting

  24. Readers and Writers Monitor ReadersNWriters { int WaitingWriters, WaitingReaders,NReaders, NWriters; Condition CanRead, CanWrite; Void BeginWrite() { if(NWriters == 1 || NReaders > 0) { ++WaitingWriters; wait(CanWrite); --WaitingWriters; } NWriters = 1; } Void EndWrite() { NWriters = 0; if(WaitingReaders) Signal(CanRead); else Signal(CanWrite); } Void BeginRead() { if(NWriters == 1 || WaitingWriters > 0) { ++WaitingReaders; Wait(CanRead); --WaitingReaders; } ++NReaders; Signal(CanRead); } Void EndRead() { if(--NReaders == 0) Signal(CanWrite); }

  25. Understanding the Solution • A reader can enter if • There are no writers active or waiting • So we can have many readers active all at once • Otherwise, a reader waits (maybe many do)

  26. Readers and Writers Monitor ReadersNWriters { int WaitingWriters, WaitingReaders,NReaders, NWriters; Condition CanRead, CanWrite; Void BeginWrite() { if(NWriters == 1 || NReaders > 0) { ++WaitingWriters; wait(CanWrite); --WaitingWriters; } NWriters = 1; } Void EndWrite() { NWriters = 0; if(WaitingReaders) Signal(CanRead); else Signal(CanWrite); } Void BeginRead() { if(NWriters == 1 || WaitingWriters > 0) { ++WaitingReaders; Wait(CanRead); --WaitingReaders; } ++NReaders; Signal(CanRead); } Void EndRead() { if(--NReaders == 0) Signal(CanWrite); }

  27. Understanding the Solution • 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)

  28. Readers and Writers Monitor ReadersNWriters { int WaitingWriters, WaitingReaders,NReaders, NWriters; Condition CanRead, CanWrite; Void BeginWrite() { if(NWriters == 1 || NReaders > 0) { ++WaitingWriters; wait(CanWrite); --WaitingWriters; } NWriters = 1; } Void EndWrite() { NWriters = 0; if(WaitingReaders) Signal(CanRead); else Signal(CanWrite); } Void BeginRead() { if(NWriters == 1 || WaitingWriters > 0) { ++WaitingReaders; Wait(CanRead); --WaitingReaders; } ++NReaders; Signal(CanRead); } Void EndRead() { if(--NReaders == 0) Signal(CanWrite); }

  29. 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

  30. Subtle aspects? • The code is “simplified” because we know there can only be one writer at a time • It also takes advantage of the fact that signal is a no-op if nobody is waiting • Where do we see these ideas used? • In the “EndWrite” code (it signals CanWrite without checking for waiting writers) • In the EndRead code (same thing) • In StartRead (signals CanRead at the end)

  31. Comparison with Semaphores • With semaphores we never did have a “fair” solution of this sort • In fact it can be done, but the code is quite tricky • Here the straightforward solution works in the desired way! • Monitors are less error-prone and also easier to understand • C# and Java primitives should typically be used in this manner, too…

More Related