Synchronization Constructs

Synchronization Ø Coordinating execution of multiple threads that share data structures

Semaphores and Monitors: Past few lectures: High-level Synchronization Constructs Ø Locks: provide mutual exclusion Ø Condition variables: provide conditional synchronization

Today: Historical perspective Ø Semaphores  Introduced by Dijkstra in 1960s  Main synchronization primitives in early operating systems Ø Monitors  Alternate high-level language constructs  Proposed by independently Hoare and Hansen in the 1970s

1 2

Semaphores Key idea of Semaphores vs. Locks

Study these for history and compatibility Ø Don’t use semaphores in new code Locks: Mutual exclusion only (1-exclusion) A non-negative integer variable with two atomic and isolated operations Semaphores: k-exclusion Ø k == 1, equivalent to a SemaphoreàP() (Passeren; wait) If sem > 0, then decrement sem by 1  Sometimes called a mutex, or binary Otherwise “wait” until sem > 0 and then decrement Ø k == 2+, up to k threads at a time

SemaphoreàV() (Vrijgeven; signal) Many semaphore implementations use “up” and “down”, Increment sem by 1 rather than Dutch names (P and V, respectively) Wake up a waiting in P() Ø ‘cause how many programmers speak Dutch?

Semaphore starts at k We assume that a semaphore is fair Ø Acquire with down(), which decrements the count Ø No thread t that is blocked on a P() operation remains blocked if the V() operation on the semaphore is invoked infinitely often  Blocks if count is 0 Ø In practice, FIFO is mostly used, transforming the set into a queue. Ø Release with up(), which increments the count and never blocks

3 4

Important properties of Semaphores

Semaphores are non-negative integers How many possible values can a binary semaphore take? The only operations you can use to change the value of a semaphore are P()/down() and V()/up() (except for the initial Ø A. 0 setup) Ø B. 1 Ø P()/down() can block, but V()/up() never blocks Ø C. 2 Semaphores are used both for Ø D. 3 Ø Mutual exclusion, and Ø E. 4 Ø Conditional synchronization

Two types of semaphores Ø Binary semaphores: Can either be 0 or 1 Ø General/Counting semaphores: Can take any non-negative value Ø Binary semaphores are as expressive as general semaphores (given one can implement the other)

5 6 Using Semaphores for Mutual Exclusion Coke Machine Example

Use a binary semaphore for mutual exclusion Coke machine as a shared buffer Semaphore = new Semaphore(1); Two types of users Ø Producer: Restocks the coke machine SemaphoreàP(); Ø Consumer: Removes coke from the machine ; SemaphoreàV(); Requirements Ø Only a single person can access the machine at any time Using Semaphores for producer-consumer with bounded buffer Ø If the machine is out of coke, wait until coke is restocked Ø If machine is full, wait for consumers to drink coke prior to restocking Use a separate int count; semaphore for How will we implement this? Semaphore mutex; each Semaphore fullBuffers; Ø How many lock and condition variables do we need? constraint Semaphore emptyBuffers;  A. 1 B. 2 C. 3 D. 4 E. 5

7 8

Revisiting Coke Machine Example Implementing Semaphores

Class CokeMachine{ Semaphore::P() { … if (value == 0) { int count; Put TCB on wait queue for semaphore; Semaphore new mutex(1); Switch(); // dispatch a ready thread Semaphores new fullBuffers(0); } Semaphores new emptyBuffers(numBuffers); else {value--;} } }

CokeMachine::Deposit(){ CokeMachine::Remove(){ emptyBuffersàP(); fullBuffersàP(); mutexàP(); à mutex P(); Semaphore::V() { Add coke to the machine; Remove coke from to the machine; if wait queue is not empty { count++; count--; Does this work? Move a waiting thread to ready queue; mutexàV(); mutexàV(); } else fullBuffersàV(); à emptyBuffers V(); value++; } } } } Does the order of P matter? Order of V matter? 9 10

Implementing Semaphores The Problem with Semaphores

Semaphore::P() { Semaphores are used for dual purpose while (value == 0) { Ø Mutual exclusion Put TCB on wait queue for semaphore; Ø Conditional synchronization Switch(); // dispatch a ready thread } Difficult to read/develop code value--; } Waiting for condition is independent of mutual exclusion Ø Programmer needs to be clever about using semaphores

CokeMachine::Deposit(){ CokeMachine::Remove(){ emptyBuffersàP(); fullBuffersàP(); Semaphore::V() { mutexàP(); mutexàP(); if wait queue is not empty { Add coke to the machine; Remove coke from to the machine; Move a waiting thread to ready queue; count++; count--; } mutexàV(); mutexàV(); value++; fullBuffersàV(); emptyBuffersàV(); } } }

11 12 Introducing Monitors Critical Section: Monitors

Separate the concerns of mutual exclusion and conditional Basic idea: synchronization Ø Restrict programming model What is a monitor? Ø Permit access to shared variables only within a critical Ø One lock, and section Ø Zero or more condition variables for managing concurrent access to shared data General program structure General approach: Ø Entry section Ø Collect related shared data into an object/module  “Lock” before entering critical section Ø Define methods for accessing the shared data  Wait if already locked, or invariant doesn’t hold Monitors first introduced as programming language construct  Key point: synchronization may involve wait Ø Calling a method defined in the monitor automatically acquires the Ø Critical section code lock Ø Exit section Ø Examples: Mesa, Java (synchronized methods)  “Unlock” when leaving the critical section Monitors also define a programming convention Object-oriented programming style Ø Can be used in any language (C, C++, … ) Ø Associate a lock with each shared object Ø Methods that access shared object are critical sections Ø Acquire/release locks when entering/exiting a method that defines a critical section

13 14

Remember Condition Variables So what is the big idea?

Locks Ø Provide mutual exclusion (Editorial) Integrate idea of condition variable with Ø Support two methods language  Lock::Acquire() – wait until lock is free, then grab it Ø Facilitate proof  Lock::Release() – release the lock, waking up a waiter, if any Ø Avoid error-prone boiler-plate code

Condition variables Ø Support conditional synchronization Ø Three operations  Wait(): Release lock; wait for the condition to become true; reacquire lock upon return (Java wait())  Signal(): Wake up a waiter, if any (Java notify())  Broadcast(): Wake up all the waiters (Java notifyAll()) Ø Two semantics for implementation of wait() and signal()  Hoare monitor semantics  Hansen (Mesa) monitor semantics

15 16

Coke Machine – Example Monitor Monitors: Recap

Class CokeMachine{ Does the order of … aquire/while(){wait} Lock acquire and release: often incorporated into Lock lock; int count = 0; matter? method definitions on object Condition notFull, notEmpty; Ø E.g., Java’s synchronized methods } Order of release/signal Ø Programmer may not have to explicitly acquire/release matter? But, methods on a monitor object do execute under mutual exclusion CokeMachine::Deposit(){ CokeMachine::Remove(){ lockàacquire(); lockàacquire(); Introduce idea of condition variable while (count == n) { while (count == 0) { notFull.wait(&lock); } notEmpty.wait(&lock); } Add coke to the machine; Remove coke from to the machine; count++; count--; notEmpty.signal(); notFull.signal(); lockàrelease(); lockàrelease(); } }

17 18 Hoare Monitors: Semantics

Hoare monitor semantics: Ø Assume thread T1 is waiting on condition x Every monitor function should start with what? Ø Assume thread T2 is in the monitor Ø A. wait Ø Assume thread T2 calls x.signal Ø T2 gives up monitor, T2 blocks! Ø B. signal Ø T1 takes over monitor, runs Ø C. lock acquire Ø T1 gives up monitor Ø T2 takes over monitor, resumes Ø D. lock release Ø E. signalAll Example T1 T2 fn1(…) … x.wait // T1 blocks fn4(…) …

x.signal // T2 blocks // T1 resumes

à Lock release(); T2 resumes

19 20

Hansen (Mesa) Monitors: Semantics Tradeoff

Hoare Hansen Hansen monitor semantics: Claims: Signal is only a hint that the Ø Assume thread T1 waiting on condition x condition may be true Ø Cleaner, good for proofs Ø Assume thread T2 is in the monitor Ø Need to check condition again Ø When a condition variable is before proceeding Ø Assume thread T2 calls x.signal; wake up T1 signaled, it does not change Ø Can lead to synchronization bugs Ø T2 continues, finishes Ø Used in most textbooks Used by most systems (e.g., Java) Ø When T1 get a chance to run,T1 takes over monitor, runs …but Ø T1 finishes, gives up monitor Ø Inefficient implementation Benefits: Ø Efficient implementation Ø Not modular – correctness Ø Condition guaranteed to be true depends on correct use and once you are out of while ! Example: implementation of signal

fn1(…) CokeMachine::Deposit(){ CokeMachine::Deposit(){ … lockàacquire(); lockàacquire(); x.wait // T1 blocks fn4(…) if (count == n) { while (count == n) { notFull.wait(&lock); } notFull.wait(&lock); }

… Add coke to the machine; Add coke to the machine; x.signal // T2 continues count++; count++; // T2 finishes notEmpty.signal(); notEmpty.signal(); lockàrelease(); lockàrelease(); // T1 resumes } } // T1 finishes

21 22

Problems with Monitors More Monitor Headaches Nested Monitor Calls The problem

What happens when one monitor calls into another? Three processes (P1, P2, P3), and P1 & P3 Ø What happens to CokeMachine::lock if thread sleeps in communicate using a monitor M. P3 is the highest CokeTruck::Unload? priority process, followed by P2 and P1. Ø What happens if truck unloader wants a coke? 1. P1 enters M.

CokeMachine::Deposit(){ CokeTruck::Unload(){ 2. P1 is preempted by P2. lockàacquire(); lockàacquire(); 3. P2 is preempted by P3. while (count == n) { while (soda.atDoor() != coke) { notFull.wait(&lock); } cokeAvailable.wait(&lock);} 4. P3 tries to enter the monitor, and waits for the lock. truck->unload(); Unload soda closest to door; 5. P2 runs again, preventing P3 from running, Add coke to the machine; soda.pop(); subverting the priority system. count++; Signal availability for soda.atDoor(); notEmpty.signal(); lockàrelease(); A simple way to avoid this situation is to associate with lockàrelease(); } each monitor the priority of the highest priority process } which ever enters that monitor.

23 24 Comparing Semaphores and Monitors Other Interesting Topics

CokeMachine::Deposit(){ CokeMachine::Deposit(){ lockàacquire(); emptyBuffersàP(); while (count == n) { Exception handling mutexàP(); notFull.wait(&lock); } Add coke to the machine; Ø What if a process waiting in a monitor needs to time out? Add coke to the machine; count++; count++; mutexàV(); notEmpty.notify(); fullBuffersàV(); Naked notify lockàrelease(); } Ø How do we synchronize with I/O devices that do not grab } CokeMachine::Remove(){ monitor locks, but can notify condition variables. fullBuffersàP(); CokeMachine::Remove(){ mutexàP(); lockàacquire(); “ Remove coke from to the machine; Butler Lampson and David Redell, Experience with while (count == 0) { count--; Processes and Monitors in Mesa.” notEmpty.wait(&lock); } mutexàV(); Remove coke from to the machine; emptyBuffersàV(); count--; } notFull.notify(); Which is better? lockàrelease(); A. Semaphore } B. Monitors 25 26

Summary

Synchronization Ø Coordinating execution of multiple threads that share data structures

Past lectures: Ø Locks à provide mutual exclusion Ø Condition variables à provide conditional synchronization

Today: Ø Semaphores  Introduced by Dijkstra in 1960s  Two types: binary semaphores and counting semaphores  Supports both mutual exclusion and conditional synchronization Ø Monitors  Separate mutual exclusion and conditional synchronization

27