Mutual Exclusion (Review) Locks and Condition Variables
n Even with semaphores, some n A semaphore serves two purposes:
synchronization errors can occur: ● Mutual exclusion — protect shared data Honest Mistake Careless Mistake n mutex in Coke machine n milk in Too Much Milk milk–>V( ); milk–>P( ); n Always a binary semaphore if (noMilk) if (noMilk) buy milk; buy milk; ● Synchronization — temporally coordinate milk–>P( ); milk–>P( ); events (one thread waits for something, ● Other variations possible other thread signals when it’s available) n fullSlot and emptySlot in Coke machine n Solution — new language constructs n Either a binary or counting semaphore
● (Conditional) Critical region n Idea — two separate constructs:
n region v when B do S; n Variable v is a shared variable that can ● Locks — provide mutually exclusion only be accessed inside the critical region ● Condition variables — provide n Boolean expression B governs access synchronization n Statement S ( critical region) is executed only if B is true; otherwise it blocks until B ● Like semaphores, locks and condition does become true variables are language-independent, and are available in many programming ● Monitor environments 1 Fall 1998, Lecture 13 2 Fall 1998, Lecture 13
Locks Locks (cont.)
n Locks provide mutually exclusive access n Conventions:
to shared data: ● Before accessing shared data, call ● A lock can be “locked” or “unlocked” Lock::Acquire( ) on a specific lock (sometimes called “busy” and “free”) n Complain (via ASSERT) if a thread tries to Acquire a lock it already has n Operations on locks (Nachos syntax): ● After accessing shared data, call Lock:: ● Lock(*name) — create a new (initially Release( ) on that same lock unlocked) Lock with the specified name n Complain if a thread besides the one that Acquired a lock tries to Release it ● Lock::Acquire( ) — wait (block) until the lock is unlocked; then lock it n Example of using locks for mutual ● Lock::Release( ) — unlock the lock; then exclusion (here, “milk” is a lock): wake up (signal) any threads waiting on it in Lock::Acquire( ) Thread A Thread B milk–>Acquire( ); milk–>Acquire( ); n Can be implemented: if (noMilk) if (noMilk) buy milk; buy milk; ● Trivially by binary semaphores (create a milk–>Release( ); milk–>Release( ); private lock semaphore, use P and V) ● The test in threads/threadtest.cc should ● By lower-level constructs, much like work exactly the same if locks are used semaphores are implemented instead of semaphores 3 Fall 1998, Lecture 13 4 Fall 1998, Lecture 13 Locks vs. Condition Variables Condition Variables
n Consider the following code: n Condition variables coordinate events
Queue::Add( ) { Queue::Remove( ) { lock->Acquire( ); lock->Acquire( ); n Operations on condition variables add item if item on queue (Nachos syntax): lock->Release( ); remove item } lock->Release( ); ● Condition(*name) — create a new return item; instance of class Condition (a condition } variable) with the specified name n After creating a new condition, the ● Queue::Remove will only return an item if programmer must call Lock::Lock( ) to there’s already one in the queue create a lock that will be associated with that condition variable n If the queue is empty, it might be more ● Condition::Wait(conditionLock) — release desirable for Queue::Remove to wait until the lock and wait (sleep); when the thread there is something to remove wakes up, immediately try to re-acquire ● Can’t just go to sleep — if it sleeps while the lock; return when it has the lock holding the lock, no other thread can ● Condition::Signal(conditionLock) — if access the shared queue, add an item to threads are waiting on the lock, wake up it, and wake up the sleeping thread one of those threads and put it on the ● Solution: condition variables will let a ready list; otherwise do nothing thread sleep inside a critical section, by releasing the lock while the thread sleeps 5 Fall 1998, Lecture 13 6 Fall 1998, Lecture 13
Condition Variables (cont.) Using Locks and Condition Variables
n Operations (cont.): n Associated with a data structure is both a
● Condition::Broadcast(conditionLock) — if lock and a condition variable threads are waiting on the lock, wake up ● Before the program performs an operation all of those threads and put them on the on the data structure, it acquires the lock ready list; otherwise do nothing ● If it needs to wait until another operation puts the data structure into an appropriate n Important: a thread must hold the lock state, it uses the condition variable to wait before calling Wait, Signal, or Broadcast n Unbounded-buffer producer-consumer: n Can be implemented: Lock *lk; int avail = 0; ● Carefully by higher-level constructs Condition *c; (create and queue threads, sleep and /* consumer */ wake up threads as appropriate) /* producer */ while (1) { ● Carefully by binary semaphores (create while (1) { lk-> Acquire( ); and queue semaphores as appropriate, lk->Acquire( ); if (avail==0) use P and V to synchronize) produce next item c->Wait(lk); n Does this work? More on this in a few avail++; consume next item minutes… c->Signal(lk) avail--; lk->Release( ); lk->Release( ); ● Carefully by lower-level constructs, much } } like semaphores are implemented 7 Fall 1998, Lecture 13 8 Fall 1998, Lecture 13