Chapter 6 (a): Synchronization Operating System Concepts with Java – 7th Edition, Nov 15, 2006 Silberschatz, Galvin and Gagne ©2007 Module 6: Process Synchronization Background Producer/Consumer Again Race Conditions Scheduler Assumptions The Critical-Section Problem Critical Section Goals Deriving a Solution Peterson’s Algorithm Bakery Algorithm Hardware Support Operating System Concepts with Java – 7th Edition, Nov 15, 2006 6.2 Silberschatz, Galvin and Gagne ©2007 Background Concurrent access to shared data may result in data inconsistency Maintaining data consistency requires mechanisms to ensure the orderly execution of cooperating processes Suppose that we wanted to provide a solution to the consumer-producer problem that fills all the buffers. Have an integer count that tracks the number of full buffers. Initially, count is set to 0. Producer increments count after producing a buffer Consumer decrements after consuming a buffer Operating System Concepts with Java – 7th Edition, Nov 15, 2006 6.3 Silberschatz, Galvin and Gagne ©2007 Producer-Consumer • Producer • while (true) { Consumer while (true) { /* produce an item and */ /* put in nextProduced */ while (count == 0) ; // do nothing b/c empty while (count == BUFFER_SIZE) ; // do nothing b/c full nextConsumed = buffer[out]; out = (out + 1) % BUFFER_SIZE; count--; buffer [in] = nextProduced; in = (in + 1) % BUFFER_SIZE; count++; /* consume the item */ /* in nextConsumed */ } } Operating System Concepts with Java – 7th Edition, Nov 15, 2006 6.4 Silberschatz, Galvin and Gagne ©2007 Race Condition count++ could be implemented as register1 = count register1 = register1 + 1 count = register1 count-- could be implemented as register2 = count register2 = register2 - 1 count = register2 Consider this execution interleaving with “count = 5” initially: S0: producer execute register1 = count {register1 = 5} S1: producer execute register1 = register1 + 1 {register1 = 6} S2: consumer execute register2 = count {register2 = 5} S3: consumer execute register2 = register2 - 1 {register2 = 4} S4: producer execute count = register1 {count = 6 } S5: consumer execute count = register2 {count = 4} Operating System Concepts with Java – 7th Edition, Nov 15, 2006 6.5 Silberschatz, Galvin and Gagne ©2007 What happened? Threads (and sometimes processes) share global memory When a process contains multiple threads, they have Private registers and stack memory (the context switching mechanism needs to save and restore registers when switching from thread to thread) Shared access to the remainder of the process “state” This can result in race conditions Operating System Concepts with Java – 7th Edition, Nov 15, 2006 6.6 Silberschatz, Galvin and Gagne ©2007 Two threads, one counter Popular web server Uses multiple threads to speed things up. Simple shared state error: each thread increments a shared counter to track number of hits … hits = hits + 1; … What happens when two threads execute concurrently? Operating System Concepts with Java – 7th Edition, Nov 15, 2006 6.7 Silberschatz, Galvin and Gagne ©2007 Shared counters Possible result: lost update! hits = 0 time read hits (0) hits = 0 + 1 hits = 1 T2 T1 read hits (0) hits = 0 + 1 One other possible result: everything works. Difficult to debug Called a “race condition” Operating System Concepts with Java – 7th Edition, Nov 15, 2006 6.8 Silberschatz, Galvin and Gagne ©2007 Race conditions Def: a timing dependent error involving shared state Whether it happens depends on how threads scheduled In effect, once thread A starts doing something, it needs to “race” to finish it because if thread B looks at the shared memory region before A is done, it may see something inconsistent Hard to detect: All possible schedules (permutations) have to be safe Number of possible schedule permutations is huge Some bad schedules? Some that will work sometimes? Intermittent Unpredictable Timing dependent = small changes can hide bug If a bug is deterministic and repeatable, celebrate! Operating System Concepts with Java – 7th Edition, Nov 15, 2006 6.9 Silberschatz, Galvin and Gagne ©2007 Scheduler Assumptions Process b: while(i > -10) i = i - 1; print “B won!”; Process a: while(i < 10) i = i +1; print “A won!”; If i is shared, and initialized to 0 Who wins? Is it guaranteed that someone wins? What if each thread runs on a separate, identical speed CPU? executing in parallel Operating System Concepts with Java – 7th Edition, Nov 15, 2006 6.10 Silberschatz, Galvin and Gagne ©2007 Scheduler Assumptions Normally we assume that A scheduler always gives every executable thread opportunities to run In effect, each thread makes finite progress But schedulers aren’t always fair Some threads may get more chances than others To reason about worst case behavior we sometimes think of the scheduler as an adversary trying to “mess up” the algorithm Operating System Concepts with Java – 7th Edition, Nov 15, 2006 6.11 Silberschatz, Galvin and Gagne ©2007 Critical Section Problem Problem: Design a protocol for processes to cooperate, such that only one process is in its critical section at any time How to make multiple instructions seem like one? Process 1 CS1 Process 2 CS2 Time Processes progress with non-zero speed, no assumption on clock speed Used extensively in operating systems: Queues, shared variables, interrupt handlers, etc. Operating System Concepts with Java – 7th Edition, Nov 15, 2006 6.12 Silberschatz, Galvin and Gagne ©2007 Critical-Section Problem 1. 2. 3. 4. Race Condition - When there is concurrent access to shared data and the final outcome depends upon order of execution. Critical Section - Section of code where shared data is accessed. Entry Section - Code that requests permission to enter its critical section. Exit Section - Code that is run after exiting the critical section Operating System Concepts with Java – 7th Edition, Nov 15, 2006 6.13 Silberschatz, Galvin and Gagne ©2007 Critical Section Goals Threads do some stuff but eventually might try to access shared data time T2 T1 CSEnter(); Critical section CSExit(); T2 T1 Operating System Concepts with Java – 7th Edition, Nov 15, 2006 CSEnter(); Critical section CSExit(); 6.14 Silberschatz, Galvin and Gagne ©2007 Critical Section Goals Perhaps they loop (perhaps not!) T2 T1 CSEnter(); Critical section CSExit(); T2 T1 Operating System Concepts with Java – 7th Edition, Nov 15, 2006 CSEnter(); Critical section CSExit(); 6.15 Silberschatz, Galvin and Gagne ©2007 Critical Section Goals We would like Safety (aka mutual exclusion) No more than one thread can be in a critical section at any time. Liveness (aka progress) A thread that is seeking to enter the critical section will eventually succeed Bounded waiting A bound must exist on the number of times that other threads are allowed to enter their critical sections after a thread has made a request to enter its critical section and before that request is granted Assume that each process executes at a nonzero speed No assumption concerning relative speed of the N processes Ideally we would like fairness as well If two threads are both trying to enter a critical section, they have equal chances of success … in practice, fairness is rarely guaranteed Operating System Concepts with Java – 7th Edition, Nov 15, 2006 6.16 Silberschatz, Galvin and Gagne ©2007 Solving the problem A first idea: Have a boolean flag, inside. Initially false. CSEnter() { while(inside) continue; inside = true; } CSExit() { inside = false; } Code is not safe: thread 0 could finish the while test when inside is false, but then thread 1 might call CSEnter() before thread 0 can set inside to true! Now ask: Is this Safe? Live? Bounded waiting? Operating System Concepts with Java – 7th Edition, Nov 15, 2006 6.17 Silberschatz, Galvin and Gagne ©2007 Solving the problem: Take 2 A different idea (assumes just two threads): Have a boolean flag, inside[i]. Initially false. CSEnter(int i) { inside[i] = true; while(inside[j]) continue; } CSExit(int i) { inside[i] = false; } Code isn’t live (doesn’t guarantee progress): with bad luck, both threads could be looping, with 0 looking at 1, and 1 looking at 0 Now ask: Is this Safe? Live? Bounded waiting? Operating System Concepts with Java – 7th Edition, Nov 15, 2006 6.18 Silberschatz, Galvin and Gagne ©2007 Solving the problem: Take 3 Another broken solution, for two threads Have a turn variable, turn, initially 1. CSEnter(int i) { while(turn != i) continue; } CSExit(int i) { turn = i ^ 1; } Code isn’t live: thread 1 can’t enter unless thread 0 did first, and vice-versa. But perhaps one thread needs to enter many times and the other fewer times, or not at all Now ask: Is this Safe? Live? Bounded waiting? Operating System Concepts with Java – 7th Edition, Nov 15, 2006 6.19 Silberschatz, Galvin and Gagne ©2007 Peterson’s Algorithm (1981) CSExit(int i) { inside[i] = false; } CSEnter(int i) { inside[i] = true; turn = J; while(inside[J] && turn == J) continue; } Now ask: Is this Safe? Live? Bounded waiting? Operating System Concepts with Java – 7th Edition, Nov 15, 2006 6.20 Silberschatz, Galvin and Gagne ©2007 Peterson’s Solution Two process solution Assume that the LOAD and STORE instructions are atomic; that is, cannot be interrupted. The two processes share two variables: int turn; Boolean inside[2] The variable turn indicates whose turn it is to enter the critical section. The inside array is used to indicate if a process is ready to enter the critical section. inside[i] = true implies that process Pi is ready! Operating System Concepts with Java – 7th Edition, Nov 15, 2006 6.21 Silberschatz, Galvin and Gagne ©2007 Analyzing Peterson’s Algorithm Safety (by contradiction): Assume that both processes (Alice and Bob) are in their critical section (and thus have their inside flags set). Since only one, say Alice, can have the turn, the other (Bob) must have reached the while() test before Alice set her inside flag. However, after setting his inside flag, Alice gave away the turn to Bob. Bob has already changed the turn and cannot change it again, contradicting our assumption. Liveness & Bounded waiting => the turn variable. Operating System Concepts with Java – 7th Edition, Nov 15, 2006 6.22 Silberschatz, Galvin and Gagne ©2007 Generalize to N Threads? Obvious approach won’t work: CSExit(int i) CSEnter(int i) { { inside[i] = false; inside[i] = true; } for(J = 0; J < N; J++) while(inside[J] && turn == J) continue; } Issue: Who’s turn is next? Operating System Concepts with Java – 7th Edition, Nov 15, 2006 6.23 Silberschatz, Galvin and Gagne ©2007 Bakery “concept” Think of a popular store with a crowded counter, perhaps the cheese line at Zabar’s People take a ticket from a machine If nobody is waiting, tickets don’t matter When several people are waiting, ticket order determines order in which they can make purchases Operating System Concepts with Java – 7th Edition, Nov 15, 2006 6.24 Silberschatz, Galvin and Gagne ©2007 Bakery Algorithm: “Take 1” int ticket[n]; int next_ticket; CSEnter(int i) { ticket[i] = ++next_ticket; for(J = 0; J < N; J++) while(ticket[J] && ticket[J] < ticket[i]) continue; } CSExit(int i) { ticket[i] = 0; } Oops… access to next_ticket is a problem! Operating System Concepts with Java – 7th Edition, Nov 15, 2006 6.25 Silberschatz, Galvin and Gagne ©2007 Bakery Algorithm: “Take 2” int ticket[n]; Just add 1 to the max! CSEnter(int i) CSExit(int i) { { ticket[i] = max(ticket[0], … ticket[N-1])+1; ticket[i] = 0; for(J = 0; J < N; J++) } while(ticket[J] && ticket[j] < ticket[i]) continue; } Clever idea: just add one to the max. Oops… two could pick the same value! Operating System Concepts with Java – 7th Edition, Nov 15, 2006 6.26 Silberschatz, Galvin and Gagne ©2007 Bakery Algorithm: “Take 3” If i, j pick same ticket value, id’s break tie: (ticket[J] < ticket[i]) || (ticket[J]==ticket[i] && J<i) Notation: (B,J) < (A,i) to simplify the code: (B<A || (B==A && J<i)), e.g.: (ticket[J],J) < (ticket[i],i) Operating System Concepts with Java – 7th Edition, Nov 15, 2006 6.27 Silberschatz, Galvin and Gagne ©2007 Bakery Algorithm: “Take 4” int ticket[N]; boolean picking[N] = false; CSEnter(int i) CSExit(int i) { { ticket[i] = max(ticket[0], … ticket[N-1])+1; ticket[i] = 0; for(J = 0; J < N; J++) } while(ticket[J] && (ticket[J],J) < (ticket[i],i)) continue; } Oops… i could look at J when J is still storing its ticket, and yet J could have a lower id than me (i)! Operating System Concepts with Java – 7th Edition, Nov 15, 2006 6.28 Silberschatz, Galvin and Gagne ©2007 Bakery Algorithm: Almost final int ticket[N]; boolean choosing[N] = false; CSEnter(int i) CSExit(int i) { { choosing[i] = true; ticket[i] = 0; ticket[i] = max(ticket[0], … ticket[N-1])+1; } choosing[i] = false; for(J = 0; J < N; J++) { while(choosing[J]) continue; while(ticket[J] && (ticket[J],J) < (ticket[i],i)) continue; } } Operating System Concepts with Java – 7th Edition, Nov 15, 2006 6.29 Silberschatz, Galvin and Gagne ©2007 Bakery Algorithm: Issues? What if we don’t know how many threads might be running? The algorithm depends on having an agreed upon value for N Somehow would need a way to adjust N when a thread is created or one goes away Also, technically speaking, ticket can overflow! Solution: Change code so that if ticket is “too big”, set it back to zero and try again. Operating System Concepts with Java – 7th Edition, Nov 15, 2006 6.30 Silberschatz, Galvin and Gagne ©2007 Bakery Algorithm: Final int ticket[N]; /* Important: Disable thread scheduling when changing N */ boolean choosing[N] = false; CSEnter(int i) { CSExit(int i) do { { ticket[i] = 0; ticket[i] = 0; choosing[i] = true; } ticket[i] = max(ticket[0], … ticket[N-1])+1; choosing[i] = false; } while(ticket[i] >= MAXIMUM); for(J = 0; J < N; J++) { while(choosing[J]) continue; while(ticket[J] && (ticket[J],J) < (ticket[i],i)) continue; } } Operating System Concepts with Java – 7th Edition, Nov 15, 2006 6.31 Silberschatz, Galvin and Gagne ©2007 Approaches to Critical Sections Everything we’ve seen so far is a software-only solution that relies on reads and writes being atomic Atomic = non-interruptible Another approach: disable interrupts Disable interrupts briefly when calling CSEnter() and CSExit() Currently running code would execute without preemption Available only in the kernel (why?) Generally doesn’t work on multiprocessor systems Operating systems using this not broadly scalable Modern machines provide hardware “help”: atomic instructions Either test memory word and set value (test and set) Or swap contents of two memory words (compare and swap) Idea is to provide a mechanism for critical sections: a lock Operating System Concepts with Java – 7th Edition, Nov 15, 2006 6.32 Silberschatz, Galvin and Gagne ©2007 Critical Section Using Locks Operating System Concepts with Java – 7th Edition, Nov 15, 2006 6.33 Silberschatz, Galvin and Gagne ©2007 TestAndSet Instruction Definition: boolean TestAndSet (boolean *target) { boolean rv = *target; *target = TRUE; return rv: } Operating System Concepts with Java – 7th Edition, Nov 15, 2006 6.34 Silberschatz, Galvin and Gagne ©2007 Solution using TestAndSet Shared boolean variable lock., initialized to false. Solution: while (true) { while ( TestAndSet (&lock )) ; /* do nothing // critical section lock = FALSE; // remainder section } Operating System Concepts with Java – 7th Edition, Nov 15, 2006 6.35 Silberschatz, Galvin and Gagne ©2007 Critical Sections using TestAndSet cs_enter: TSL REGISTER, LOCK // copy lock to reg and set to 1 CMP REGISTER, #0 // was lock 0? JNE cs_enter // if so, loop RET // otherwise return, in critical sec cs_exit: STORE LOCK #0 RET // set lock to 0 Operating System Concepts with Java – 7th Edition, Nov 15, 2006 6.36 Silberschatz, Galvin and Gagne ©2007 Swap Instruction Definition: void Swap (boolean *a, boolean *b) { boolean temp = *a; *a = *b; *b = temp: } Operating System Concepts with Java – 7th Edition, Nov 15, 2006 6.37 Silberschatz, Galvin and Gagne ©2007 Solution using Swap Shared Boolean variable lock initialized to FALSE; Each process has a local Boolean variable key. Solution: while (true) { key = TRUE; while ( key == TRUE) Swap (&lock, &key ); // critical section lock = FALSE; // remainder section } Operating System Concepts with Java – 7th Edition, Nov 15, 2006 6.38 Silberschatz, Galvin and Gagne ©2007 Critical Sections using Swap cs_enter: MOVE REGISTER, #1 XCHG REGISTER, LOCK CMP REGISTER, #0 JNE cs_enter RET // // // // // cs_exit: MOVE LOCK, #0 RET // store a zero in lock // return to caller Operating System Concepts with Java – 7th Edition, Nov 15, 2006 put 1 in register swap reg and lock contents was lock zero? if not, loop otherwise return, in cs 6.39 Silberschatz, Galvin and Gagne ©2007 Providing Critical Sections to Users Everything we’ve seen so far involves busy waiting Also known as spin locks Acceptable for short waits (e.g., interrupts) Need a general-purpose mechanism that allows sleeping Would like to provide higher-level abstractions to users CSEnter and CSExit are possibilities Operating systems have offer other primitives E.g., semaphores, condition variables, mutexes Built out of low-level critical section operations More in next class Operating System Concepts with Java – 7th Edition, Nov 15, 2006 6.40 Silberschatz, Galvin and Gagne ©2007 End of Chapter 6 (a) Operating System Concepts with Java – 7th Edition, Nov 15, 2006 Silberschatz, Galvin and Gagne ©2007