<<

Review: Synchronization problem with Threads CS162 • One per transaction, each running: Operating Systems and Deposit(acctId, amount) { acct = GetAccount(actId); /* May use disk I/O */ Systems Programming acct->balance += amount; Lecture 8 StoreAccount(acct); /* Involves disk I/O */ } • Unfortunately, shared state can get corrupted: Semaphores, Monitors, and Thread 1 Thread 2 Readers/Writers load r1, acct->balance load r1, acct->balance add r1, amount2 February 18th, 2015 store r1, acct->balance add r1, amount1 Prof. John Kubiatowicz store r1, acct->balance http://cs162.eecs.Berkeley.edu • Atomic Operation: an operation that always runs to completion or not at all – It is indivisible: it cannot be stopped in the middle and state cannot be modified by someone else in the middle

2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.2

Review: Too Much Milk Solution #3 Review: Too Much Milk: Solution #4 • Here is a possible two-note solution: • Suppose we have some sort of implementation of a Thread A Thread B (more in a moment). leave note A; leave note B; – Acquire(&mylock) – wait until lock is free, then grab while (note B) {\\X if (noNote A) {\\Y – Release(&mylock) – Unlock, waking up anyone waiting do nothing; if (noMilk) { } buy milk; – These must be atomic operations – if two threads are if (noMilk) { } waiting for the lock and both see it’s free, only one buy milk; } succeeds to grab the lock } remove note B; remove note A; • Then, our milk problem is easy: • Does this work? Yes. Both can guarantee that: Acquire(&milklock); – It is safe to buy, or if (nomilk) – Other will buy, ok to quit buy milk; Release(&milklock); • At X: – if no note B, safe for A to buy, • Once again, section of code between Acquire() and Release() called a “” – otherwise wait to find out what will happen • Of course, you can make this even simpler: suppose • At Y: you are out of ice cream instead of milk – if no note A, safe for B to buy – Skip the test since you always need more ice cream. – Otherwise, A is either buying or waiting for B to quit 2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.3 2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.4 Recall: Better Implementation of Locks Goals for Today by Disabling

• Continue with Synchronization Abstractions • Key idea: maintain a lock variable and impose mutual exclusion only during operations on that variable – Semaphores, Monitors, and Condition variables int mylock = FREE; • Readers-Writers problem and solution Acquire(&mylock) – wait until lock is free, then grab • Introduction to Release(&mylock) – Unlock, waking up anyone waiting Acquire(int *lock) { Release(int *lock) { disable interrupts; disable interrupts; if (*lock == BUSY) { if (anyone on wait queue) { put thread on wait queue; take thread off wait queue Go to sleep(); Place on ready queue; } else { // Enable interrupts? *lock = FREE; } else { } *lock = BUSY; enable interrupts; } } Note: Some slides and/or pictures in the following are enable interrupts; adapted from slides ©2005 Silberschatz, Galvin, and GagneGagne. } Many slides generated from my lecture notes by Kubiatowicz. • Really only works in kernel – why? 2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.5 2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.6

Recall: How to Re-enable After Sleep()? Review: Examples of Read-Modify-Write • test&set (&address) { /* most architectures */ • Interrupts are disabled when you call sleep: result = M[address]; – Responsibility of the next thread to re-enable ints M[address] = 1; return result; – When the sleeping thread wakes up, returns to acquire } and re-enables interrupts • swap (&address, register) { /* x86 */ temp = M[address]; Thread A Thread B M[address] = register; . register = temp; . } disable ints • compare&swap (&address, reg1, reg2) { /* 68000 */ sleep if (reg1 == M[address]) { sleep return M[address] = reg2; return success; enable ints } else { . return failure; . } . } disable int • load-linked&store conditional(&address) { sleep /* R4000, alpha */ loop: sleep return ll r1, M[address]; enable ints movi r2, 1; /* Can do arbitrary comp */ . sc r2, M[address]; . beqz r2, loop; • Why must Interrupts be disabled during switch? } 2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.7 2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.8 Implementing Locks with test&set Problem: Busy-Waiting for Lock • A flawed, but simple solution (that works at user-level!) • Positives for this solution – Machine can receive interrupts int mylock = 0; // Free – User code can use this lock Acquire() { while (test&set(&mylock)); // while busy – Works on a multiprocessor } • Negatives Release() { – This is very inefficient because the busy-waiting mylock = 0; thread will consume cycles waiting } – Waiting thread may take cycles away from thread • Simple explanation: holding lock (no one wins!) – If lock is free, test&set reads 0 and sets value=1, so lock – : If busy-waiting thread has higher is now busy. It returns 0 so while exits. priority than thread holding lock  no progress! – If lock is busy, test&set reads 1 and sets value=1 (no • Priority Inversion problem with original Martian rover change). It returns 1, so while loop continues • For semaphores and monitors, waiting thread may – When we set value = 0, someone else can get lock wait for an arbitrary length of time! • Issues with this solution – Thus even if busy-waiting was OK for locks, definitely not ok for other primitives – Busy-Waiting: thread consumes cycles while waiting – Homework/exam solutions should not have busy-waiting! – Does not take advantage of multi-core/processor caches!

2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.9 2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.10

Multiprocessor Spin Locks: test&test&set Better Locks using test&set • A better solution for multiprocessors: • Can we build test&set locks without busy-waiting? int mylock = 0; // Free – Can’t entirely, but can minimize! Acquire() { – Idea: only busy-wait to atomically check lock value do { int guard = 0; int mylock = FREE; while(mylock); // Wait until might be free } while(test&set(&mylock)); // exit if get lock Acquire(&mylock) – wait until lock is free, then grab Release(&mylock) – Unlock, waking up anyone waiting } Acquire(int *lock) { Release(int *lock) { Release() { // Short busy-wait time // Short busy-wait time mylock = 0; while (test&set(&guard)); while (test&set(&guard)); } if (*lock == BUSY) { if anyone on wait queue { • Simple explanation: put thread on wait queue; take thread off wait queue – Wait until lock might be free (only reading – stays in cache) go to sleep() & guard = 0; Place on ready queue; } else { } else { – Then, try to grab lock with test&set *lock = FREE; *lock = BUSY; – Repeat if fail to actually get lock } guard = 0; guard = 0; • Issues with this solution: } – Busy-Waiting: thread still consumes cycles while waiting } » However, it does not impact other processors! • Note: sleep has to be sure to reset the guard variable – Why can’t we do it just before or just after the sleep? 2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.11 2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.12 Administrivia Using of Compare&Swap for queues • compare&swap (&address, reg1, reg2) { /* 68000 */ • First Checkpoint due this Friday 11:59pm PST if (reg1 == M[address]) { – Yes this is graded! M[address] = reg2; return success; – Assume design document is high level! } else { » You should think of this as a document for a manager return failure; (your TA) } } • Do your own work! – Please do not try to find solutions from previous terms Here is an atomic add to linked-list function: – We will be look out for this… addToQueue(&object) { do { // repeat until no conflict • Basic semaphores work in PintOS! ld r1, M[root] // Get ptr to current head – However, you will need to implement priority scheduling st r1, M[object] // Save link in new object behavior both in semaphore and ready queue } until (compare&swap(&root,r1,object)); } • Still could use more folks in Thursday 12-1 and root next next Friday 10-1 sections! – Much better next – Try to attend the section with your project TA…? New Object 2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.13 2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.14

Higher-level Primitives than Locks Semaphores • Goal of last couple of lectures: • Semaphores are a kind of generalized lock – What is the right abstraction for synchronizing threads – First defined by Dijkstra in late 60s that share memory? – Main synchronization primitive used in original UNIX – Want as high a level primitive as possible • Definition: a Semaphore has a non-negative integer • Good primitives and practices important! value and supports the following two operations: – Since execution is not entirely sequential, really hard to – P(): an atomic operation that waits for semaphore to find bugs, since they happen rarely become positive, then decrements it by 1 – UNIX is pretty stable now, but up until about mid-80s » Think of this as the wait() operation (10 years after started), systems running UNIX would – V(): an atomic operation that increments the semaphore crash every week or so – concurrency bugs by 1, waking up a waiting P, if any • Synchronization is a way of coordinating multiple » This of this as the signal() operation concurrent activities that are using shared state – Note that P() stands for “proberen” (to test) and V() stands for “verhogen” (to increment) in Dutch – This lecture and the next presents a couple of ways of structuring the sharing

2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.15 2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.16 Semaphores Like Integers Except Two Uses of Semaphores • Semaphores are like integers, except • Mutual Exclusion (initial value = 1) – No negative values – Also called “Binary Semaphore”. – Only operations allowed are P and V – can’t read or write – Can be used for mutual exclusion: value, except to set it initially semaphore.P(); // Critical section goes here – Operations must be atomic semaphore.V(); » Two P’s together can’t decrement value below zero • Scheduling Constraints (initial value = 0) » Similarly, thread going to sleep in P won’t miss wakeup – Locks are fine for mutual exclusion, but what if you from V – even if they both happen at same time want a thread to wait for something? • Semaphore from railway analogy – Example: suppose you had to implement ThreadJoin – Here is a semaphore initialized to 2 for resource control: which must wait for thread to terminiate: Initial value of semaphore = 0 ThreadJoin { semaphore.P(); } ThreadFinish { semaphore.V(); Value=2Value=1Value=0 }

2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.17 2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.18

Producer-consumer with a bounded buffer Correctness constraints for solution • Correctness Constraints: Producer Buffer Consumer – Consumer must wait for producer to fill buffers, if none • Problem Definition full (scheduling constraint) – Producer puts things into a shared buffer – Producer must wait for consumer to empty buffers, if all – Consumer takes them out full (scheduling constraint) – Need synchronization to coordinate producer/consumer – Only one thread can manipulate buffer queue at a time • Don’t want producer and consumer to have to work in (mutual exclusion) lockstep, so put a fixed-size buffer between them • Remember why we need mutual exclusion – Need to synchronize access to this buffer – Because computers are stupid – Producer needs to wait if buffer is full – Imagine if in real life: the delivery person is filling the – Consumer needs to wait if buffer is empty machine and somebody comes up and tries to stick their • Example 1: GCC compiler money into the machine – cpp | cc1 | cc2 | as | ld • General rule of thumb: • Example 2: Coke machine Use a separate semaphore for each constraint – Producer can put limited number of cokes in machine – Semaphore fullBuffers; // consumer’s constraint – Consumer can’t take cokes out if machine is empty – Semaphore emptyBuffers;// producer’s constraint – Semaphore mutex; // mutual exclusion

2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.19 2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.20 Full Solution to Bounded Buffer Discussion about Solution Semaphore fullBuffer = 0; // Initially, no coke • Why asymmetry? Semaphore emptyBuffers = numBuffers; // Initially, num empty slots – Producer does: emptyBuffer.P(), fullBuffer.V() Semaphore mutex = 1; // No one using machine – Consumer does: fullBuffer.P(), emptyBuffer.V() • Is order of P’s important? Producer(item) { – Yes! Can cause deadlock: emptyBuffers.P(); // Wait until space Producer(item) { mutex.P(); // Wait until buffer free mutex.P(); // Wait until buffer free Enqueue(item); mutex.V(); emptyBuffers.P(); // Could wait forever! fullBuffers.V(); // Tell consumers there is Enqueue(item); // more coke mutex.V(); } fullBuffers.V(); // Tell consumers more coke Consumer() { } fullBuffers.P(); // Check if there’s a coke • Is order of V’s important? mutex.P(); // Wait until machine free – No, except that it might affect scheduling efficiency item = Dequeue(); mutex.V(); • What if we have 2 producers or 2 consumers? emptyBuffers.V(); // tell producer need more – Do we need to change anything? return item; }

2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.21 2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.22

Motivation for Monitors and Condition Variables Monitor with Condition Variables • Semaphores are a huge step up, but: – They are confusing because they are dual purpose: » Both mutual exclusion and scheduling constraints » Example: the fact that flipping of P’s in bounded buffer gives deadlock is not immediately obvious – Cleaner idea: Use locks for mutual exclusion and condition variables for scheduling constraints • Definition: Monitor: a lock and zero or more condition variables for managing concurrent access to shared data • Lock: the lock provides mutual exclusion to shared data – Use of Monitors is a programming paradigm – Always acquire before accessing shared data structure – Some languages like Java provide monitors in the – Always release after finishing with shared data language – Lock initially free • The lock provides mutual exclusion to shared data: • Condition Variable: a queue of threads waiting for – Always acquire before accessing shared data structure something inside a critical section – Always release after finishing with shared data – Key idea: make it possible to go to sleep inside critical – Lock initially free section by atomically releasing lock at time we go to sleep – Contrast to semaphores: Can’t wait inside critical section 2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.23 2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.24 Simple Monitor Example (version 1) Condition Variables • Here is an (infinite) synchronized queue • How do we change the RemoveFromQueue() routine to Lock lock; wait until something is on the queue? Queue queue; – Could do this by keeping a count of the number of things on the queue (with semaphores), but error prone AddToQueue(item) { • Condition Variable: a queue of threads waiting for lock.Acquire(); // Lock shared data something inside a critical section queue.enqueue(item); // Add item lock.Release(); // Release Lock – Key idea: allow sleeping inside critical section by } atomically releasing lock at time we go to sleep – Contrast to semaphores: Can’t wait inside critical section RemoveFromQueue() { lock.Acquire(); // Lock shared data • Operations: item = queue.dequeue();// Get next item or null – Wait(&lock): Atomically release lock and go to sleep. lock.Release(); // Release Lock Re-acquire lock later, before returning. return(item); // Might return null } – Signal(): Wake up one waiter, if any • Not very interesting use of “Monitor” – Broadcast(): Wake up all waiters – It only uses a lock with no condition variables • Rule: Must hold lock when doing condition variable ops! – In Birrell paper, he says can perform signal() outside of – Cannot put consumer to sleep if no work! lock – IGNORE HIM (this is only an optimization)

2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.25 2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.26

Complete Monitor Example (with condition variable) vs. Hoare monitors • Here is an (infinite) synchronized queue • Need to be careful about precise definition of signal and wait. Consider a piece of our dequeue code: Lock lock; Condition dataready; while (queue.isEmpty()) { Queue queue; dataready.wait(&lock); // If nothing, sleep } item = queue.dequeue(); // Get next item AddToQueue(item) { – Why didn’t we do this? lock.Acquire(); // Get Lock queue.enqueue(item); // Add item if (queue.isEmpty()) { dataready.signal(); // Signal any waiters dataready.wait(&lock); // If nothing, sleep lock.Release(); // Release Lock } } item = queue.dequeue(); // Get next item • Answer: depends on the type of scheduling RemoveFromQueue() { – Hoare-style (most textbooks): lock.Acquire(); // Get Lock » Signaler gives lock, CPU to waiter; waiter runs immediately while (queue.isEmpty()) { dataready.wait(&lock); // If nothing, sleep » Waiter gives up lock, processor back to signaler when it } exits critical section or if it waits again item = queue.dequeue(); // Get next item – Mesa-style (most real operating systems): lock.Release(); // Release Lock » Signaler keeps lock and processor return(item); » Waiter placed on ready queue with no special priority } » Practically, need to check condition again after wait 2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.27 2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.28 Recall: CPU Scheduling Scheduling Assumptions • CPU scheduling big area of research in early 70’s • Many implicit assumptions for CPU scheduling: – One program per user – One thread per program – Programs are independent • Clearly, these are unrealistic but they simplify the problem so it can be solved – For instance: is “fair” about fairness among users or • Earlier, we talked about the life-cycle of a thread programs? – Active threads work their way from Ready queue to » If I run one compilation job and you run five, you get five Running to various waiting queues. times as much CPU on many operating systems • Question: How is the OS to decide which of several • The high-level goal: Dole out CPU time to optimize tasks to take off a queue? some desired parameters of system – Obvious queue to worry about is ready queue – Others can be scheduled as well, however USER1 USER2 USER3 USER1 USER2 • Scheduling: deciding which threads are given access to resources from moment to moment Time

2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.29 2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.30

Scheduling Policy Goals/Criteria First-Come, First-Served (FCFS) Scheduling • Minimize Response Time • First-Come, First-Served (FCFS) – Minimize elapsed time to do an operation (or job) – Also “First In, First Out” (FIFO) or “Run until done” – Response time is what the user sees: » In early systems, FCFS meant one program » Time to echo a keystroke in editor scheduled until done (including I/O) » Time to compile a program » Now, means keep CPU until thread blocks » Real-time Tasks: Must meet deadlines imposed by World • Example: Burst Time • Maximize P1 24 P2 3 – Maximize operations (or jobs) per second P3 3 – Throughput related to response time, but not identical: – Suppose processes arrive in the order: P1 , P2 , P3 » Minimizing response time will lead to more context The Gantt Chart for the is: switching than if you only maximized throughput – Two parts to maximizing throughput P1 P2 P3 » Minimize overhead (for example, context-switching) » Efficient use of resources (CPU, disk, memory, etc) 0 24 27 30 • Fairness – Waiting time for P1 = 0; P2 = 24; P3 = 27 – Share CPU among users in some equitable way – Average waiting time: (0 + 24 + 27)/3 = 17 – Fairness is not minimizing average response time: – Average Completion time: (24 + 27 + 30)/3 = 27 » Better average response time by making system less fair • Convoy effect: short process behind long process 2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.31 2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.32 FCFS Scheduling (Cont.) Round Robin (RR) • Example continued: • FCFS Scheme: Potentially bad for short jobs! – Depends on submit order – Suppose that processes arrive in order: P2 , P3 , P1 Now, the Gantt chart for the schedule is: – If you are first in line at supermarket with milk, you don’t care who is behind you, on the other hand… P2 P3 P1 • Round Robin Scheme – Each process gets a small unit of CPU time 0 3306 (time quantum), usually 10-100 milliseconds – Waiting time for P1 = 6;P2 = 0; P3 = 3 – After quantum expires, the process is preempted – Average waiting time: (6 + 0 + 3)/3 = 3 and added to the end of the ready queue. – n processes in ready queue and time quantum is q  – Average Completion time: (3 + 6 + 30)/3 = 13 » Each process gets 1/n of the CPU time • In second case: » In chunks of at most q time units – average waiting time is much better (before it was 17) » No process waits more than (n-1)q time units – Average completion time is better (before it was 27) • Performance – q large  FCFS • FIFO Pros and Cons: – q small  Interleaved (really small  hyperthreading?) – Simple (+) – q must be large with respect to , – Short jobs get stuck behind long ones (-) otherwise overhead is too high (all overhead) » Safeway: Getting milk, always stuck behind cart full of small items. Upside: get to read about space aliens! 2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.33 2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.34

Example of RR with Time Quantum = 20 Round-Robin Discussion • Example: Process Burst Time • How do you choose time slice? P1 53 P 8 – What if too big? 2 » Response time suffers P3 68 P4 24 – What if infinite ()? – The Gantt chart is: » Get back FIFO – What if time slice too small? P1 P2 P3 P4 P1 P3 P4 P1 P3 P3 » Throughput suffers! 0 20 28 48 68 88 108 112 125 145 153 • Actual choices of timeslice: – Initially, UNIX timeslice one second: – Waiting time for P1=(68-20)+(112-88)=72 P =(20-0)=20 » Worked ok when UNIX was used by one or two people. 2 » What if three compilations going on? 3 seconds to echo P3=(28-0)+(88-48)+(125-108)=85 P =(48-0)+(108-68)=88 each keystroke! 4 – In practice, need to balance short-job performance – Average waiting time = (72+20+85+88)/4=66¼ and long-job throughput: – Average completion time = (125+28+153+112)/4 = 104½ » Typical time slice today is between 10ms – 100ms • Thus, Round-Robin Pros and Cons: » Typical context-switching overhead is 0.1ms – 1ms – Better for short jobs, Fair (+) » Roughly 1% overhead due to context-switching – Context-switching time adds up for long jobs (-)

2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.35 2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.36 Comparisons between FCFS and Round Robin Earlier Example with Different Time Quantum • Assuming zero-cost context-switching time, is RR P2 P4 P1 P3 always better than FCFS? Best FCFS: [8] [24] [53] [68] • Simple example: 10 jobs, each take 100s of CPU time 0 8 32 85 153 RR scheduler quantum of 1s All jobs start at the same time Quantum P1 P2 P3 P4 Average • Completion Times: Job # FIFO RR Best FCFS 32 0 85 8 31¼ 1 100 991 Q = 1 84 22 85 57 62 Q = 5 82 20 85 58 61¼ 2 200 992 Wait Q = 8 80 8 85 56 57¼ ……… Time Q = 10 82 10 85 68 61¼ 9 900 999 Q = 20 72 20 85 88 66¼ 10 1000 1000 Worst FCFS 68 145 0 121 83½ – Both RR and FCFS finish at the same time Best FCFS 85 8 153 32 69½ – Average response time is much worse under RR! Q = 1 137 30 153 81 100½ Q = 5 135 28 153 82 99½ » Bad when all jobs same length Completion Q = 8 133 16 153 80 95½ • Also: Cache state must be shared between all jobs with Time RR but can be devoted to each job with FIFO Q = 10 135 18 153 92 99½ Q = 20 125 28 153 112 104½ – Total time for RR longer even for zero-cost switch! Worst FCFS 121 153 68 145 121¾ 2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.37 2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.38

First peak at responsiveness scheduler: Assumption: CPU Bursts Multi-Level Feedback Scheduling

Long-Running Compute Tasks Demoted to Weighted toward small bursts Low Priority

• A method for exploiting past behavior – First used in CTSS – Multiple queues, each with different priority » Higher priority queues often considered “foreground” tasks • Execution model: programs alternate between bursts of – Each queue has its own scheduling algorithm CPU and I/O » e.g. foreground – RR, background – FCFS » Sometimes multiple RR priorities with quantum increasing – Program typically uses the CPU for some period of time, exponentially (highest:1ms, next:2ms, next: 4ms, etc) then does I/O, then uses CPU again • Adjust each job’s priority as follows (details vary) – Each scheduling decision is about which job to give to the CPU for use by its next CPU burst – Job starts in highest – With timeslicing, thread may be forced to give up CPU – If timeout expires, drop one level before finishing current CPU burst – If timeout doesn’t expire, push up one level (or to top) 2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.39 2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.40 Break Extended example: Readers/Writers Problem W

R R R

• Motivation: Consider a shared database – Two classes of users: » Readers – never modify database » Writers – read and modify database – Is using a single lock on the whole database sufficient? » Like to have many readers at the same time » Only one writer at a time

2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.41 2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.42

Basic Readers/Writers Solution Code for a Reader • Correctness Constraints: Reader() { – Readers can access database when no writers // First check self into system – Writers can access database when no readers or writers lock.Acquire(); – Only one thread manipulates state variables at a time while ((AW + WW) > 0) { // Is it safe to read? • Basic structure of a solution: WR++; // No. Writers exist – Reader() okToRead.wait(&lock); // Sleep on cond var Wait until no writers WR--; // No longer waiting Access data base } Check out – wake up a waiting writer AR++; // Now we are active! – Writer() lock.release(); Wait until no active readers or writers Access database // Perform actual read-only access Check out – wake up waiting readers or writer AccessDatabase(ReadOnly); – State variables (Protected by a lock called “lock”): // Now, check out of system » int AR: Number of active readers; initially = 0 lock.Acquire(); » int WR: Number of waiting readers; initially = 0 AR--; // No longer active » int AW: Number of active writers; initially = 0 if (AR == 0 && WW > 0) // No other active readers » int WW: Number of waiting writers; initially = 0 okToWrite.signal(); // Wake up one writer » Condition okToRead = NIL lock.Release(); » Conditioin okToWrite = NIL }

2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.43 2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.44 Code for a Writer Simulation of Readers/Writers solution Writer() { • Consider the following sequence of operators: // First check self into system lock.Acquire(); – R1, R2, W1, R3 while ((AW + AR) > 0) { // Is it safe to write? • On entry, each reader checks the following: WW++; // No. Active users exist while ((AW + WW) > 0) { // Is it safe to read? okToWrite.wait(&lock); // Sleep on cond var WR++; // No. Writers exist WW--; // No longer waiting okToRead.wait(&lock); // Sleep on cond var } WR--; // No longer waiting AW++; // Now we are active! } lock.release(); AR++; // Now we are active! // Perform actual read/write access AccessDatabase(ReadWrite); • First, R1 comes along: // Now, check out of system AR = 1, WR = 0, AW = 0, WW = 0 lock.Acquire(); • Next, R2 comes along: AW--; // No longer active AR = 2, WR = 0, AW = 0, WW = 0 if (WW > 0){ // Give priority to writers okToWrite.signal(); // Wake up one writer • Now, readers make take a while to access database } else if (WR > 0) { // Otherwise, wake reader okToRead.broadcast(); // Wake all readers – Situation: Locks released } – Only AR is non-zero lock.Release(); } 2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.45 2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.46

Simulation(2) Simulation(3)

• Next, W1 comes along: • When writer wakes up, get: while ((AW + AR) > 0) { // Is it safe to write? WW++; // No. Active users exist AR = 0, WR = 1, AW = 1, WW = 0 okToWrite.wait(&lock); // Sleep on cond var • Then, when writer finishes: WW--; // No longer waiting } if (WW > 0){ // Give priority to writers AW++; okToWrite.signal(); // Wake up one writer } else if (WR > 0) { // Otherwise, wake reader • Can’t start because of readers, so go to sleep: okToRead.broadcast(); // Wake all readers AR = 2, WR = 0, AW = 0, WW = 1 } • Finally, R3 comes along: – Writer wakes up reader, so get: AR = 2, WR = 1, AW = 0, WW = 1 AR = 1, WR = 0, AW = 0, WW = 0 • Now, say that R2 finishes before R1: • When reader completes, we are finished AR = 1, WR = 1, AW = 0, WW = 1 • Finally, last of first two readers (R1) finishes and wakes up writer: if (AR == 0 && WW > 0) // No other active readers okToWrite.signal(); // Wake up one writer

2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.47 2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.48 Questions Can we construct Monitors from Semaphores? • Can readers starve? Consider Reader() entry code: • Locking aspect is easy: Just use a mutex while ((AW + WW) > 0) { // Is it safe to read? • Can we implement condition variables this way? WR++; // No. Writers exist Wait() { semaphore.P(); } okToRead.wait(&lock); // Sleep on cond var WR--; // No longer waiting Signal() { semaphore.V(); } } – Doesn’t work: Wait() may sleep with lock held AR++; // Now we are active! • Does this work better? • What if we erase the condition check in Reader exit? Wait(Lock lock) { lock.Release(); AR--; // No longer active semaphore.P(); if (AR == 0 && WW > 0) // No other active readers lock.Acquire(); okToWrite.signal(); // Wake up one writer } • Further, what if we turn the signal() into broadcast() Signal() { semaphore.V(); } AR--; // No longer active – No: Condition vars have no history, semaphores have okToWrite.broadcast(); // Wake up one writer history: • Finally, what if we use only one condition variable (call » What if thread signals and no one is waiting? NO-OP it “okToContinue”) instead of two separate ones? » What if thread later waits? Thread Waits » What if thread V’s and noone is waiting? Increment – Both readers and writers sleep on this variable » What if thread later does P? Decrement and continue – Must use broadcast() instead of signal()

2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.49 2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.50

Construction of Monitors from Semaphores (con’t) Monitor Conclusion • Problem with previous try: • Monitors represent the logic of the program – P and V are commutative – result is the same no matter what order they occur – Wait if necessary – Condition variables are NOT commutative – Signal when change something so any waiting threads can proceed • Does this fix the problem? Wait(Lock lock) { • Basic structure of monitor-based program: lock.Release(); lock semaphore.P(); while (need to wait) { Check and/or update lock.Acquire(); condvar.wait(); state variables } } Wait if necessary Signal() { unlock if semaphore queue is not empty semaphore.V(); do something so no need to wait } – Not legal to look at contents of semaphore queue lock – There is a race condition – signaler can slip in after lock release and before waiter executes semaphore.P() condvar.signal(); Check and/or update state variables • It is actually possible to do this correctly unlock – Complex solution for Hoare scheduling in book – Can you come up with simpler Mesa-scheduled solution? 2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.51 2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.52 C-Language Support for Synchronization C++ Language Support for Synchronization • Languages with exceptions like C++ • C language: Pretty straightforward synchronization – Languages that support exceptions are problematic (easy – Just make sure you know all the code paths out of a to make a non-local exit without releasing lock) critical section

Stack growth – Consider: int Rtn() { Proc A lock.acquire(); void Rtn() { … Proc B lock.acquire(); if (exception) { Calls setjmp … lock.release(); DoFoo(); return errReturnCode; Proc C lock.acquire … } lock.release(); … lock.release(); Proc D } return OK; void DoFoo() { } Proc E … Calls longjmp – Watch out for setjmp/longjmp! if (exception) throw errException; … » Can cause a non-local jump out of procedure } » In example, procedure E calls longjmp, poping stack back to procedure B – Notice that an exception in DoFoo() will exit without releasing the lock » If Procedure C had lock.acquire, problem!

2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.53 2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.54

C++ Language Support for Synchronization (con’t) Java Language Support for Synchronization • Must catch all exceptions in critical sections – Catch exceptions, release lock, and re-throw exception: • Java has explicit support for threads and thread void Rtn() { synchronization lock.acquire(); • Bank Account example: try { class Account { … private int balance; DoFoo(); // object constructor … public Account (int initialBalance) { } catch (…) { // catch exception balance = initialBalance; lock.release(); // release lock } throw; // re-throw the exception public synchronized int getBalance() { } return balance; lock.release(); } } public synchronized void deposit(int amount) { void DoFoo() { balance += amount; … } if (exception) throw errException; } … – Every object has an associated lock which gets } automatically acquired and released on entry and exit – Even Better: auto_ptr facility. See C++ Spec. from a synchronized method. » Can deallocate/free lock regardless of exit method

2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.55 2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.56 Java Language Support for Synchronization (con’t) Java Language Support for Synchronization (con’t 2) • Java also has synchronized statements: • In addition to a lock, every object has a single condition variable associated with it synchronized (object) { – How to wait inside a synchronization method of block: … » void wait(long timeout); // Wait for timeout } » void wait(long timeout, int nanoseconds); //variant – Since every Java object has an associated lock, this » void wait(); type of statement acquires and releases the object’s – How to signal in a synchronized method or block: lock on entry and exit of the body » void notify(); // wakes up oldest waiter – Works properly even with exceptions: » void notifyAll(); // like broadcast, wakes everyone synchronized (object) { – Condition variables can wait for a bounded length of … time. This is useful for handling exception cases: DoFoo(); t1 = time.now(); … while (!ATMRequest()) { wait (CHECKPERIOD); } t2 = time.new(); void DoFoo() { if (t2 – t1 > LONG_TIME) checkMachine(); throw errException; } } – Not all Java VMs equivalent! » Different scheduling policies, not necessarily preemptive!

2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.57 2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.58

Summary • Semaphores: Like integers with restricted interface – Two operations: » P(): Wait if zero; decrement when becomes non-zero » V(): Increment and wake a sleeping (if exists) » Can initialize value to any non-negative value – Use separate semaphore for each constraint • Monitors: A lock plus one or more condition variables – Always acquire lock before accessing shared data – Use condition variables to wait inside critical section » Three Operations: Wait(), Signal(),andBroadcast() • Scheduling: selecting a waiting process from the ready queue and allocating the CPU to it • FCFS Scheduling: – Run threads to completion in order of submission – Pros: Simple – Cons: Short jobs get stuck behind long ones • Round-Robin Scheduling: – Give each thread a small amount of CPU time when it executes; cycle between all ready threads – Pros: Better for short jobs – Cons: Poor when jobs are same length

2/18/15 Kubiatowicz CS162 ©UCB Spring 2015 Lec 8.59