Review The Critical Section problem Petersons Algorithm Implementing
Review • • • The Critical Section problem Peterson’s Algorithm Implementing Atomicity Busy waiting Blocking
Outline • Semaphores – what they are – how they are used • Monitors – what they are – how they are used
What’s wrong with this picture? Our solutions to CS have two unappealing features: – They are mistifying and unclear – They use busy waiting
Semaphores A semaphore consists of: • A variable s • A thread set T • A function P (Passeren; wait) • A function V (Vrijgeven; signal) syntax example: Semaphore s(5);
Semaphore Operations Initialization(val) { s = val ; T = ; }
P(s) s--; if(s < 0) { add caller thread to T; block; } This code executes atomically.
V(s) s++; if(s 0) { Remove a thread t from T; Unblock(t); } This code executes atomically.
Mutual exclusion with semaphores Thread T 0 while (!terminate) { P(s); 0> <entry CS <exit V(s); 0> NCS 0 } Thread T 1 while (!terminate) { P(s); > <entry 1 CS <exit 1> V(s); NCS 1 } init: s = 1
Questions, Questions. . . • What if we have n threads instead of 2? • What if we want to allow at most k threads in the CS?
Binary Semaphore Two types: • Counting semaphores – s takes any values • Binary semaphores – s takes only 0 or 1
Binary Semaphores P(s) if (s == 0) { Add caller thread to T; Block; } else s--; V(s) if (T != ) { Remove a thread t from T; Unblock(t); } else if (s == 0) s = 1;
Important properties of Semaphores -I • Semaphores are non-negative integers • The only operations you can use to change the value of a semaphore are P() and V() (except for the initial setup) • Semaphores have two uses – Mutual exclusion: you know all about it – Synchronization: how do we make sure that T 1 completes execution before T 2 starts?
Important Properties of Semaphores- II • We assume that a semaphore is fair: No thread t that is blocked because of a P(s) operation remains blocked if s is V’d infinitely often Does this guarantee bounded waiting? • In practice, FIFO is mostly used, transforming the set into a queue. • V(s) never blocks. • Binary semaphores are as expressive as general semaphores (given one can implement the other) – But they are different: {s = 0} V(s) P(s) {s = 0} Vb(s) Pb(s) VS
Classic Problems: Producer-Consumer I • • • Producer adds items to a shared buffer Consumer takes them out Buffer avoids producers and consumers to proceed in lock step • Buffer is finite Examples: – – cpp|cc|as coke machine ready queue in a multithreaded multiprocessor ….
Producer Consumer II • Two types of constraint: – Mutual Exclusion • Only one thread can manipulate the buffer at any one time – Condition Synchronization • Consumer must wait if buffer is empty • Producer must wait if buffer is full • Are these safety or liveness properties?
Producer Consumer III • Use a separate semaphore for each constraint Semaphore mutex; Semaphore full. Slots; Semaphore empty. Slots; // protects the shared buffer // counts full slots: if 0, no // coke // counts empty slots: if 0, // nowhere to put more coke
Producer Consumer IV Semaphore new mutex (1); Semaphore new empty. Slots(num. Slots); Semaphore new full. Slots(0); Producer() { P(empty. Slots); P(mutex); put 1 coke in the machine V(mutex); V(full. Slots); } Is the order of Ps important? Consumer() { P(full. Slots); P(mutex); take a coke out V(mutex); V(empty. Slots); } Is the order of Vs important?
Beyond Semaphores • Semaphores huge step forward (immagine having to do producer consumer with Peterson’s algorithm…) • BUT…Semaphores are used both for mutex and condition synchronization – hard to read code – hard to get code right
Monitors (late 60’s, early 70’s) • A programming language construct, much like what we call today objects • Consists of: – – General purpose variables Methods Initialization code (constructor) Condition variables • Can implement a monitor-like style of programming without the language construct, using a combination of locks and condition variables
Locks A lock provides mutual exclusion to shared data: Lock: : Acquire() – wait until lock is free, then grab it Lock: : Release() – unlock; wake up anyone waiting in Acquire Rules: – Always acquire before accessing a shared data structure – Always release after finishing with shared data structure – Lock is initially free
Example: Producer Consumer add. To. Buff() { lock. Acquire(); // lock before using shared data put item on buffer, if there is space lock. Release(); } remove. From. Buff() { lock. Acquire(); // lock before using shared data if something on buffer, remove it lock. Release(); return item; }
Houston, we have a problem… • How do we change remove. From. Buff so that it check if there is something in the buffer before trying to remove it? • Logically, want to suspend thread inside the critical section • What is the problem? • Solution: suspend atomically release lock
Condition Variables A queue of threads waiting for something inside the critical section (why does it not violate safety? ) Condition variables have two operations: – Wait() • Calling thread blocks, releases lock (gives up monitor) • Wait on a queue until someone signals variable – Signal() • Unblock someone who was waiting on the variable – Broadcast() • Unblock all waiting on the variable
Producer Consumer, again add. To. Buff() { lock. Acquire(); // lock before using shared data put item on buffer condition. signal(); lock. Release(); } remove. From. Buff() { lock. Acquire(); // lock before using shared data while nothing on buffer { condition. wait(&lock) } remove item from buffer lock. Release(); return item; }
A dilemma • What happens to a thread that signals on a condition variable while in the middle of a critical section?
Hoare Monitors • • Assume thread Q waiting on condition x Assume thread P is in the monitor Assume thread P calls x. signal P gives up monitor, P blocks! Q takes over monitor, runs Q finishes, gives up monitor P takes over monitor, resumes
Example: Hoare Monitors fn 1(…) … x. wait // T 1 blocks // T 1 resumes // T 1 finishes fn 4(…) … x. signal // T 2 blocks // T 2 resumes
Per Brinch Hansen’s Monitors • • • Assume thread Q waiting on condition x Assume thread P is in the monitor Assume thread P calls x. signal P continues, finishes Q takes over monitor, runs Q finishes, gives up monitor
Example: Hansen Monitors fn 1(…) … x. wait // T 1 blocks // T 1 resumes // T 1 finishes fn 4(…) … x. signal // T 2 continues // T 2 finishes
Tradeoff Hoare Hansen • Awkward in programming • Cleaner, good for mathematical proofs • Main advantage: when a condition variable is signalled, condition does not change • Used bymost textbooks • Easier to program • Can lead to synchronization bugs • Used by most systems
Classic Problems 2: Readers-Writers • Motivation: shared database (bank, seat reservation system) • Two classes of users: – readers (never modify database) – writers (read and modify database) • Want to maximize concurrency – at most one writer in database – if no writer, any number of readers in database
Towards a solution State variables: – ar --- active readers (readers in CS) – aw --- active writers (writers in CS) Condition variables: – oktoread (readers can enter critical section) – oktowrite (writer can enter critical section) Safety ar ≥ 0 aw ≥ 0 (aw = 1 ar = 0))
Structure of the solution Database: : read() wait until no writers access database if no readers in database, let writer in Database: : write() wait until no readers or writers access database wake up waiting readers and writers Use procedures start. Read, done. Read, start. Write, done. Write
The procedures Database: : start. Read() { lock. Acquire(); while (aw) { ok. To. Read. Wait(&lock); } ar: = ar+1; ok. To. Read. signal; lock. Release(); } Database: : done. Read() { lock. Acquire(); ar : =ar – 1; if (ar =0) {ok. To. Write. signal} lock. Release(); } Database: : start. Write() { lock. Acquire(); while (aw ar) { ok. To. Write. Wait(&lock) } aw : = 1; lock. Release(); } Database: : done. Write() { lock. Acquire(); aw : = 0; ok. To. Read. signal; ok. To. Write. signal; lock. Release(); }
Semaphores and Monitors - I Can we build monitors using semaphores? Try 1: Wait() {P(s)} Signal() {V(s)} Try 2: Wait(Lock *lock) { lock. Release(); P(s); lock. Acquire(); } Signal() { V(s); }
Semaphores and Monitors-II Try 3: Signal() { if semaphore queue is not empty {V(s)} } REMEMBER: – If a thread signals on a condition on which no thread is waiting, the result is a no-op. – If a thread executes a V on a semaphore on which no thread is waiting, the semaphore is incremented!
A few matters of style • • Always do things the same way Always use monitors (condition variables plus locks) Always hold lock when operating on a condition variable Always grab lock at the beginning of a procedure and release it before return • Always use while (not if) to check for the value of a condition variable • (Almost) never use sleep to synchronize
Example: Java • Each object has a monitor • User can specify the keyword “synchronized” in front of methods that must execute with mutual exclusion • Use Hansen monitors • No individually named condition variables (just one anonymous condition variable) – wait() – notify. All()
- Slides: 38