170 likes | 277 Views
More Synchronization, Semaphores. Vivek Pai / Kai Li Princeton University. Continuing on Synchronization. So far, we’ve seen “Spinning” on lock during entire critical section Disabling interrupts for critical section (bad) Queue associated with each lock & blocking
E N D
More Synchronization, Semaphores Vivek Pai / Kai Li Princeton University
Continuing on Synchronization So far, we’ve seen • “Spinning” on lock during entire critical section • Disabling interrupts for critical section (bad) • Queue associated with each lock & blocking • System calls for locking – possibly blocking Since system calls exist, is everything solved? Assume shared variable “count” Lock(&mutex); count++; Unlock(&mutex);
Cost of Protecting a Shared Variable • Making lock system call • Pushing parameter, sys call # onto stack • Generating trap/interrupt to enter kernel • System call in kernel • Jump to appropriate function in kernel • Verify process passed in valid pointer to mutex • Do locking operation, block process if needed • Actually change count – load/modify/store • System call again to release mutex
What is Lock Contention? • Competition for a lock • Uncontended = rarely in use by someone else • Contended = often used by someone else • Held = currently in use by someone • Question: what do these combinations do? • Spinning on low-contention lock • Spinning on high-contention lock • Blocking on low-contention lock • Blocking on high-contention lock
Things to Ponder • If critical section is just “count++;”, what is the overhead of the synchronization • Is there some other way of doing this? • What if you don’t know how long the critical section will be?
What If You Have the Following • Test-and-set – works at either user or kernel • System calls for block/unblock • Block takes some token and goes to sleep • Unblock “wakes up” a waiter on token
User-Level Acquire/Release using Block and Unblock • In what scenarios is this scheme appropriate? • Where should it not be used? Release(lock) { lock = 0; Unblock( lock ); } Acquire(lock) { while (!TAS(lock)) Block( lock ); }
Semaphores (Dijkstra, 1965) • Up or “V” • Atomic • Increment semaphore by 1 wake up a waiting P if any • Down or “P” • Atomic • Wait for semaphore to become positive and then decrement by 1 P(s) { if (--s < 0) Block(s); } V(s) { if (++s <= 0) Unblock(s); }
Bounded Buffer (Consumer-Producer) • Example: • grep vivek access.log | more Producer Consumer
Bounded Buffer w/ Semaphores mutex = 1 emptyCount = N; fullCount = 0; producer() { while (1) { produce an item P(emptyCount); P(mutex); put the item in buffer V(mutex); V(fullCount); } } consumer() { while (1) { P(fullCount); P(mutex); take an item from buffer V(mutex); V(emptyCount); consume the item } }
Implementing General Semaphores • Need a mutex for each semaphore • Block and Unblock need to release mutex after entering their critical section V(s) { Acquire(s.mutex); if (++s.value <= 0) Unblock(s); else Release(s.mutex) } P(s) { Acquire(s.mutex); if (--s.value < 0) Block(s); else Release(s.mutex) }
Implement General Semaphores with Acquire/Release • Kotulski (1988) • Two processes call P(s) (when s.value is 0) and preempted after Release(s.mutex) • Two other processes call V(s) P(s) { Acquire(s.mutex); if (--s.value < 0) { Release(s.mutex); Acquire(s.delay); } else Release(s.mutex); } V(s) { Acquire(s.mutex); if (++s.value <= 0) Release(s.delay); Release(s.mutex); }
Hemmendinger’s Solution (1988) • The idea is not to release s.mutex and turn it over individually to the waiting process • P and V are executing in lockstep P(s) { Acquire(s.mutex); if (--s.value < 0) { Release(s.mutex); Acquire(s.delay); } Release(s.mutex); } V(s) { Acquire(s.mutex); if (++s.value <= 0) Release(s.delay); else Release(s.mutex); }
Kearns’s Solution (1988) P(s) { Acquire(s.mutex); if (--s.value < 0) { Release(s.mutex); Acquire(s.delay); Acquire(s.mutex); if (--s.wakecount > 0) Release(s.delay); } Release(s.mutex); } V(s) { Acquire(s.mutex); if (++s.value <= 0) { s.wakecount++; Release(s.delay); } Release(s.mutex); } Two Release( s.delay) calls are also possible
Hemmendinger’s Correction (1989) P(s) { Acquire(s.mutex); if (--s.value < 0) { Release(s.mutex); Acquire(s.delay); Acquire(s.mutex); if (--s.wakecount > 0) Release(s.delay); } Release(s.mutex); } V(s) { Acquire(s.mutex); if (++s.value <= 0) { s.wakecount++; if (s.wakecount == 1) Release(s.delay); } Release(s.mutex); } Correct but a complex solution
Hsieh’s Solution (1989) P(s) { Acquire(s.delay); Acquire(s.mutex); if (--s.value > 0) Release(s.delay); Release(s.mutex); } V(s) { Acquire(s.mutex); if (++s.value == 1) Release(s.delay); Release(s.mutex); } • Use Acquire(s.delay) to block processes • Correct but still a constrained implementation