210 likes | 387 Views
Synchronization. Where are we going with synchronization?. 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. Programs.
E N D
Where are we going with synchronization? 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 Programs Higher-level API Hardware Shared Programs Locks Semaphores Monitors Load/Store Disable Ints Test&Set Comp&Swap
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 booleanTestAndSet(boolean &target) { booleanrv = 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 Voperation 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: semaphoremutex; //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 toS.L;block(); } V(S): S.value++; if (S.value<= 0) { remove a process PifromS.L;wakeup(Pi); }
Semaphore as a General Synchronization Tool • Execute <B> in Pjonly after <A> executed in Pi • Use semaphore flaginitialized 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 Qbe two semaphores initialized to 1 Pi Pj P(S) P(Q) P(Q) P(S) V(S) V(Q) V(Q) V(S)