1 / 21

Inside Blocking Synchronization

Learn about implementing mutexes in the Nachos operating system and the potential issues that can arise when not done correctly.

groesbeck
Download Presentation

Inside Blocking Synchronization

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. Inside Blocking Synchronization

  2. Administrative • “I need more space to build Nachos.” • “Ask and ye shall receive.” • Lab 1 grades: • Now available on the course Web. • Lab 2: • Will be due THURSDAY at MIDNIGHT. • actually, 12:01 AM on Friday • (I don’t want you sleeping through MY class.) • Sign up for demos on the Web after Thursday’s class.

  3. Implementing Mutexes: A First Cut class Lock { int held; List sleepers; } void Lock::Acquire() { while (held) { Why the while loop? sleepers.Append((void*)currentThread); currentThread->Sleep(); } held = 1; Is this safe? } void Lock::Release() { held = 0; if (!sleepers->IsEmpty()) /* somebody’s waiting: wake up */ scheduler->ReadyToRun((Thread*)sleepers->Remove()); }

  4. Mutexes: What Went Wrong void Lock::Acquire() { while (held) { sleepers.Append((void*)currentThread); currentThread->Sleep(); } held = 1; } void Lock::Release() { held = 0; if (!sleepers->IsEmpty()) /* somebody’s waiting: wake up */ scheduler->ReadyToRun((Thread*)sleepers->Remove()); } Potential corruption of sleepers list in a race between two Acquires or an Acquire and a Release. Potential missed wakeup: holder could Release before thread is on sleepers list. Potential missed wakeup: holder could call to wake up before we are “fully asleep”. Race to acquire: two threads could observe held == 0 concurrently, and think they both can acquire the lock.

  5. Example: Await/Awake • Consider a very simple use of sleep/wakeup to implement two new primitives: • currentThread->Await() • Block the calling thread. • Thread::Awake() • If the target thread is sleeping in a previous Await(), wake it up. • Else leave it alone.

  6. Await/Awake Using Sleep/Wakeup void Thread::Await() { ASSERT(this == currentThread); ASSERT(!awaiting); awaiting = TRUE; /* “I’m sleeping” */ Sleep(); /* sleep */ } void Thread::Awake() { ASSERT(this != currentThread); if (awaiting) /* wakeup */ scheduler->ReadyToRun(this); awaiting = FALSE; /* “you’re awake” */ } missed wakeup double wakeup These sleep/wakeup races are sometimes referred to as the wake-up waiter problem.

  7. Using Sleep/Wakeup Safely void Thread::Await() { disable interrupts; awaiting = TRUE; /* “I’m sleeping” */ Sleep(); /* sleep */ enable interrupts; } void Thread::Awake() { disable interrupts; if (awaiting) { /* wakeup */ scheduler->ReadyToRun(this); awaiting = FALSE; /* “you’re awake” */ enable interrupts; } Nachos Sleeprequires disabling interrupts, in part to avoid missed wakeup. How can it be OK to sleep with interrupts disabled? Will this work on a multiprocessor?

  8. disable interrupts enable interrupts Sleep and Yield in Nachos Context switch itself is a critical section, which we enter only via Sleep or Yield. Disable interrupts on the call to Sleep or Yield, and rely on the “other side” to re-enable on return from its own Sleep or Yield. Sleep() { ASSERT(getLevel = IntOff); this->status = BLOCKED; next = scheduler->FindNextToRun(); while(next = NULL) { /* idle */ next = scheduler->FindNextToRun(); } scheduler->Run(next); } Yield() { IntStatus old = SetLevel(IntOff); next = scheduler->FindNextToRun(); if (next != NULL) { scheduler->ReadyToRun(this); scheduler->Run(next); } interrupt->SetLevel(old); }

  9. What to Know about Sleep/Wakeup • 1. Sleep/wakeup primitives are the fundamental basis for all blocking synchronization. • 2. Correct use of sleep/wakeup requires some additional low-level mechanism to avoid missed and double wakeups. • disabling interrupts, and/or • constraints on preemption, and/or • spin-waiting with TSL or equivalent • 3. These low-level mechanisms are tricky and error-prone. • 4. High-level “magic” synchronization primitives take care of the details of using sleep/wakeup. • semaphores, mutexes, condition variables

  10. Spin-Yield: Just Say No void Thread::Await() { awaiting = TRUE; while(awaiting) Yield(); } void Thread::Awake() { if (awaiting) awaiting = FALSE; }

  11. The “Magic” of Semaphores and CVs • Any use of sleep/wakeup synchronization can be replaced with semaphores or condition variables. • Most uses of blocking synchronization have some associated state to record the blocking condition. • e.g., list or count of waiting threads, or a table or count of free resources, or the completion status of some operation, or.... • The trouble with sleep/wakeup is that the program must update the state atomically with the sleep/wakeup. • Semaphores integrate the state into atomic P/V primitives. • ....but the only state that is supported is a simple counter. • Condition variables (CVs) allow the program to define the condition/state, and protect it with an integrated mutex.

  12. P1() P2() P3() P4() The Roots of Condition Variables: Monitors A monitor is a module (a collection of procedures) in which execution is serialized. [Brinch Hansen 1973, C.A.R. Hoare 1974] CVs are easier to understand if we think about them in terms of the original monitor formulation. Java supports a variant of monitors called synchronized methods. state At most thread may be active in the monitor at a time. (enter) ready to enter (exit) A thread may wait in the monitor, allowing another thread to enter. signal() A thread in the monitor may signal a waiting thread, causing it to return from its wait and reenter the monitor. wait() blocked

  13. P1() P2() P3() P4() Hoare Semantics Suppose purple signals blue in the previous example. suspended Hoare semantics: the signalled thread immediately takes over the monitor, and the signaller is suspended. state signal() (Hoare) (enter) ready to enter (exit) The signaller cannot continue in the monitor until the signalled thread exits or waits again. signal() (Hoare) Hoare semantics allow the signalled thread to assume that the state has not changed since the signal that woke it up. wait() blocked

  14. P1() P2() P3() P4() Mesa Semantics Suppose again that purple signals blue in the original example. Mesa semantics: the signalled thread transitions back to the ready state. state There is no suspended state: the signaller continues until it exits the monitor or waits. (enter) ready to enter (exit) The signalled threadeventually enters the monitor again and returns from its wait. signal() (Mesa) Mesa semantics are easier to understand and implement... BUT: the signalled thread must examine the monitor state again after the wait, as the state may have changed since the signal. wait() blocked Loop before you leap!

  15. From Monitors to Nachos • Nachos mutexes and condition variables are based on monitors, but are more flexible. • A monitor is “just like” a module whose state includes a mutex and a condition variable. • It’s “just as if” the module’s methods Acquire the mutex on entry and Release the mutex before returning. • With mutexes, the critical regions within the methods can be defined at a finer grain, to allow more concurrency. • With condition variables, the module methods may wait and signal on multiple independent conditions. • Nachos (and Topaz and Java) use Mesa semantics for their condition variables: loop before you leap!

  16. Semaphores using Condition Variables void Down() { mutex->Acquire(); ASSERT(count >= 0); while(count == 0) condition->Wait(mutex); count = count - 1; mutex->Release(); } void Up() { mutex->Acquire(); count = count + 1; condition->Signal(mutex); mutex->Release(); } (Loop before you leap!) This constitutes a proof that mutexes and condition variables are just as powerful as semaphores.

  17. Semaphores vs. Condition Variables • 1. Up differs from Signal in that: • Signal has no effect if no thread is waiting on the condition. • Condition variables are not variables! They have no value! • Up has the same effect whether or not a thread is waiting. • Semaphores retain a memory of all calls to Up. • 2. Down differs from Wait in that: • Down checks the condition and blocks only if necessary. • no need to recheck the condition after returning from Down • wait condition is defined internally, but is limited to a counter • Wait is explicit: it does not check the condition, ever. • condition is defined externally and protected by integrated mutex

  18. Example: A Counting Queue void CountingQueue::Await() { mx->Acquire(); count++; cv->Wait(); mx->Release(); } void CountingQueue::Awake() { mx->Acquire(); if (count > 0) { count--; cv->Signal(); } mx->Release(); } What do we assume about Wait/Signal? Will this work using Birrell’s CV semantics? Birrell claims that (given Mesa semantics) it is acceptable for a signal to wake up more than one waiter. (“wake up at least one”)

  19. Example: A Better Counting Queue void CountingQueue::Await() { mx->Acquire(); count++; cv->Wait(); count--; mx->Release(); } void CountingQueue::Awake() { mx->Acquire(); if (count > 0) cv->Signal(); mx->Release(); } This version will work even if condition variables have Birrell’s “wake up at least one” semantics. How would we do this with a semaphore?

  20. Counting Queue with Semaphores mx->Init(1); cv->Init(0); void CountingQueue::Await() { mx->P(); count++; cv->P(); count--; mx->V(); } void CountingQueue::Awake() { mx->P(); if (count > 0) cv->V(); mx->V(); } • Replace our mutex with a binary semaphore. • Replace our condition variable with a semaphore whose value is always zero, so P always sleeps and V always wakes up. • But the resulting code is harder to understand.

  21. Example: Ping-Pong void PingPong() { mx->Acquire(); while(not done) { cv->Signal(); cv->Wait(); } mx->Release(); } Will this work using Birrell’s CV semantics? How would you do this using semaphores? Will your Lab #2 condition variables execute this example correctly?

More Related