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); 2 Cost of Protecting a Shared Variable Making lock system call System call in kernel 3 Pushing parameter, sys call # onto stack Generating trap/interrupt to enter 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 Question: what do these combinations do? 4 Uncontended = rarely in use by someone else Contended = often used by someone else Held = currently in use by someone Spinning on low-contention lock Spinning on high-contention lock Blocking on low-contention lock Blocking on high-contention lock Things to Ponder 5 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 6 Block takes some token and goes to sleep Unblock “wakes up” a waiter on token User-Level Acquire/Release using Block and Unblock Acquire(lock) { while (!TAS(lock)) Block( lock ); } 7 Release(lock) { lock = 0; Unblock( lock ); } In what scenarios is this scheme appropriate? Where should it not be used? Semaphores (Dijkstra, 1965) Down or “P” Atomic Wait for semaphore to become positive and then decrement by 1 P(s) { if (--s < 0) Block(s); } 8 • Up or “V” – Atomic – Increment semaphore by 1 wake up a waiting P if any V(s) { if (++s <= 0) Unblock(s); } Bounded Buffer (Consumer-Producer) Example: grep vivek access.log | more Producer 9 Consumer Bounded Buffer w/ Semaphores mutex = 1 emptyCount = N; fullCount = 0; producer() { while (1) { produce an item P(emptyCount); consumer() { while (1) { P(fullCount); P(mutex); take an item from buffer V(mutex); P(mutex); put the item in buffer V(mutex); V(emptyCount); consume the item V(fullCount); } } 10 } } Implementing General Semaphores Need a mutex for each semaphore Block and Unblock need to release mutex after entering their critical section P(s) { Acquire(s.mutex); if (--s.value < 0) Block(s); else Release(s.mutex) } 11 V(s) { Acquire(s.mutex); if (++s.value <= 0) Unblock(s); else Release(s.mutex) } Implement General Semaphores with Acquire/Release P(s) { Acquire(s.mutex); if (--s.value < 0) { Release(s.mutex); Acquire(s.delay); } else Release(s.mutex); } Kotulski (1988) 12 V(s) { Acquire(s.mutex); if (++s.value <= 0) Release(s.delay); Release(s.mutex); } Two processes call P(s) (when s.value is 0) and preempted after Release(s.mutex) Two other processes call V(s) Hemmendinger’s Solution (1988) P(s) { Acquire(s.mutex); if (--s.value < 0) { Release(s.mutex); Acquire(s.delay); } Release(s.mutex); } 13 V(s) { Acquire(s.mutex); if (++s.value <= 0) Release(s.delay); else Release(s.mutex); } The idea is not to release s.mutex and turn it over individually to the waiting process P and V are executing in lockstep 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 14 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); } Correct but a complex solution 15 V(s) { Acquire(s.mutex); if (++s.value <= 0) { s.wakecount++; if (s.wakecount == 1) Release(s.delay); } Release(s.mutex); } Hsieh’s Solution (1989) P(s) { Acquire(s.delay); Acquire(s.mutex); if (--s.value > 0) Release(s.delay); Release(s.mutex); } 16 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