Semaphores and Condition Variables Locks can be used
Semaphores and Condition Variables
Locks can be used to ensure mutual exclusion in conflicting critical sections. • A lock is an object, a data item in memory. Methods: Lock: : Acquire and Lock: : Release. A • Threads pair calls to Acquire and Release. • Acquire before entering a critical section. A R • Release after leaving a critical section. R • Between Acquire/Release, the lock is held. • Acquire does not return until any previous holder releases. • Waiting locks can spin (a spinlock) or block (a mutex).
Nachos Thread States and Transitions running Thread: : Sleep (voluntary) blocked Thread: : Yield (voluntary or involuntary) Scheduler: : Run Scheduler: : Ready. To. Run ready current. Thread->Yield(); current. Thread->Sleep();
Example: Per-Thread Counts and Total /* shared by all threads */ int counters[N]; int total; /* * Increment a counter by a specified value, and keep a running sum. * This is called repeatedly by each of N threads. * tid is a thread identifier unique across all threads. * value is just some arbitrary number. */ void Touch. Count(int tid, int value) { counters[tid] += value; total += value; }
Reading Between the Lines of C load add store vulnerable between load and store of counters[tid]. . . but it’s non-shared. vulnerable between load and store of total, which is shared. /* counters[tid] += value; total += value; */ load shl add load add store counters, R 1 8(SP), R 2, #2, R 2 R 1, R 2, R 1 4(SP), R 3 (R 1), R 2, R 3, R 2, (R 1) total, R 2, R 3, R 2, total ; load counters base ; load tid index ; index = index * sizeof(int) ; compute index to array ; load value ; load counters[tid] ; counters[tid] += value ; store back to counters[tid] ; load total ; total += value ; store total
Using a Lock for the Counter/Sum Example int counters[N]; int total; Lock *lock; /* * Increment a counter by a specified value, and keep a running sum. */ void Touch. Count(int tid, int value) { lock->Acquire(); counters[tid] += value; /* critical section code is atomic. . . */ total += value; /* …as long as the lock is held */ lock->Release(); }
Portrait of a Lock in Motion R A A R
Example: A Bounded Resource int Allocate. Entry() { int i; while (!Find. Free. Item(&i)) block and wait for a free slot[i] = 1; /* grab free slot */ return(i); } void Release. Entry(int i) { slot[i] = 0; wakeup waiter, if any } boolean Find. Free. Item(int* index) { for (i = 0; i < Table. Size; i++) if (slot[i] == 0) return it; return (FALSE); }
Semaphores Mutexes are designed for mutual exclusion; they are not sufficient for higher-level synchronization needs. waiting for an event managing a resource with more than one instance Semaphores handle all of your synchronization needs with one elegant but confusing abstraction. • an integer variable with special operations and properties “souped up” increment (Up or V) and decrement (Down or P) current value is the number of resource instances free to allocate atomic/implicit sleep when decrementing a zero semaphore atomic/implicit wakeup when incrementing
A Bounded Resource with a Counting Semaphore semaphore->Init(N); int Allocate. Entry() { int i; semaphore->Down(); ASSERT(Find. Free. Item(&i)); slot[i] = 1; return(i); } void Release. Entry(int i) { slot[i] = 0; semaphore->Up(); } A semaphore for an N-way resource is called a counting semaphore. Passing a Down guarantees that a resource instance is reserved for the caller. Problems?
Semaphores as Mutexes semapohore->Init(1); void Lock: : Acquire() { semaphore->Down(); } void Lock: : Release() { semaphore->Up(); } Semaphores must be initialized with a value representing the number of free resources: mutexes are a single-use resource. Down() to acquire a resource; blocks if no resource is available. Up() to release a resource; wakes up waiter if any. Up and Down are atomic. Mutexes are often called binary semaphores. However, “real” mutexes have additional constraints on their use.
Condition Variables Condition variables allow explicit event notification. • much like a souped-up sleep/wakeup • associated with a mutex to avoid sleep/wakeup races managing events using semaphores often demands more synchronization, which is “built in” condition variables alternative to counting semaphores for resource allocation “generally safer and more flexible than semaphores” Condition: : Wait(Lock*) Condition: : Signal(Lock*) Condition: : Broadcast(Lock*)
Using Condition Variables Condition *cv; Lock* cv. Mx; int waiter = 0; Must hold lock when calling Wait atomically releases lock and sleeps until next Signal. void await() { cv. Mx->Lock(); waiter = waiter + 1; /* “I’m sleeping” */ cv->Wait(cv. Mx); /* sleep */ cv. Mx->Unlock(); } void wakeup() { cv. Mx->Lock(); if (waiter) { cv->Signal(cv. Mx); waiter = waiter - 1; } Cv. Mx->Unlock(); } Wait atomically reacquires lock before returning. Association with mutex allows threads to safely manage state related to the sleep/wakeup coordination (e. g. , waiters count).
Bounded Resource with a Condition Variable Mutex* mx; Condition *cv; int Allocate. Entry() { int i; mx->Acquire(); while(!Find. Free. Item(&i)) cv. Wait(mx); slot[i] = 1; mx->Release(); return(i); } void Release. Entry(int i) { mx->Acquire(); slot[i] = 0; cv->Signal(); mx->Release(); } “Loop before you leap. ” Why is this Acquire needed?
Semaphores with 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(); }
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
- Slides: 16