Synchronization Where are we going with synchronization? Programs Shared Programs Higher-level API Locks Semaphores Monitors Hardware Load/Store Disable Ints Test&Set Comp&Swap • We are going to implement various higher-level synchronization primitives using atomic operations – Everything is pretty painful if only atomic primitives are load and store – Need to provide primitives useful at user-level Lock • Race conditions can be prevented by requiring that critical sections be protected by locks. That is, a process must acquire a lock before entering a critical section; it releases the lock when it exits the critical section Synchronization do { [acquire lock] critical section [release lock] remainder section } while (TRUE); “Too Much Milk” Solution #4 • Suppose we have some sort of implementation of a lock – Lock.Acquire() – wait until lock is free, then grab – Lock.Release() – Unlock, waking up anyone waiting – These must be atomic operations – if two processes are waiting for the lock and both see it’s free, only one succeeds to grab the lock • Then, our milk problem is easy: milklock.Acquire(); if (nomilk) buy milk; milklock.Release(); How to implement Locks? • Lock: prevents someone from doing something – Lock before entering critical section and before accessing shared data – Unlock when leaving, after accessing shared data – Wait if locked • Important idea: all synchronization involves waiting • Should sleep if waiting for a long time • Hardware Lock instruction Synchronization Hardware • Many systems provide hardware support for critical section code • Uniprocessors – could disable interrupts – Currently running code would execute without preemption – System’s clock is kept updated by interrupts Naïve use of Interrupt Enable/Disable • How can we build multi-instruction atomic operations? – Recall: dispatcher gets control in two ways » Internal: Process does something to relinquish the CPU » External: Interrupts cause dispatcher to take CPU – On a uniprocessor, can avoid context-switching by: » Avoiding internal events (although virtual memory tricky) » Preventing external events by disabling interrupts • Consequently, naïve implementation of locks: LockAcquire { disable Ints; } LockRelease { enable Ints; } • Problems with this approach: – Can’t let user do this! Consider following: LockAcquire(); While(TRUE) {;} – What happens with I/O or other important events? » “Reactor about to meltdown. Help?” Synchronization Hardware • Modern machines provide special atomic hardware instructions • Atomic = non-interruptable • Atomic instructions that allow us either to test and modify the content of a word or to swap the contents of two words Synchronization Hardware • Test and modify the content of a word atomically boolean TestAndSet (boolean &target) { boolean rv = target; target = true; return rv; } Mutual Exclusion with Test-and-Set • Shared data: boolean lock = false; • Process Pi do { while (TestAndSet(lock)); critical section lock = false; remainder section } while (TRUE); Synchronization Hardware • Atomically swap two variables void Swap(boolean &a, boolean &b) { boolean temp = a; a = b; b = temp; } Mutual Exclusion with Swap • Shared data (initialized to false): boolean lock; • Process Pi do { key = true; // key is a local variable while (key == true) Swap(lock,key); critical section lock = false; remainder section } while (TRUE); Bounded-waiting Mutual Exclusion with TestandSet() do { waiting[i] = TRUE; key = TRUE; while (waiting[i] && key) key = TestAndSet(&lock); waiting[i] = FALSE; // critical section j = (i + 1) % n; while ((j != i) && !waiting[j]) j = (j + 1) % n; if (j == i) lock = FALSE; else waiting[j] = FALSE; // remainder section } while (TRUE); Semaphores • Dijkstra’s work on semaphores established over 30 years ago the foundation of modern techniques for accomplishing synchronization • A semaphore, S, is a integer variable that is changed or tested only by one of the two following indivisible operations P(S): while (S 0) do no-op; S--; V(S): S++; • In Dijkstra’s original paper, the P operation was an abbreviation for the Dutch word Proberen, meaning “to test” and the V operation was an abbreviation for the word Verhogen, meaning “to • increment” Now, P() and V() is normally called wait() and signal() Critical Section for n Processes • Shared data: semaphore mutex; // initially mutex = 1 • Process Pi do { P(mutex); critical section V(mutex); remainder section } while (TRUE); Semaphore Implementation • Must guarantee that no two processes can execute wait () and signal () on the same semaphore at the same time • Thus, implementation becomes the critical section problem where the wait and signal code are placed in the crtical section. – Could now have busy waiting in critical section implementation • But implementation code is short • Little busy waiting if critical section rarely occupied • Note that applications may spend lots of time in critical sections and therefore this is not a good solution. Semaphore Implementation with no Busy waiting • With each semaphore there is an associated waiting queue. Each entry in a waiting queue has two data items: – value (of type integer) – pointer to next record in the list typedef struct { int value; struct process *L; } semaphore; • Two operations: – block – place the process invoking the operation on the appropriate waiting queue. – wakeup – remove one of processes in the waiting queue and place it in the ready queue. Semaphore Implementation • Semaphore operations now defined as P(S): S.value--; if (S.value < 0) { add this process to S.L; block(); } V(S): S.value++; if (S.value <= 0) { remove a process Pi from S.L; wakeup(Pi); } Semaphore as a General Synchronization Tool • Execute <B> in Pj only after <A> executed in Pi • Use semaphore flag initialized to 0 Pi Pj <A> P(flag) V(flag) <B> Deadlock and Starvation • Deadlock – two or more processes are waiting indefinitely for an event that can be caused by only one of the waiting processes • Starvation – indefinite blocking. A process may never be removed from the semaphore queue in which it is suspended • Let S and Q be two semaphores initialized to 1 Pi Pj P(S) P(Q) P(Q) P(S) V(S) V(Q) V(Q) V(S)