1 / 41

Chapter 6 (a): Synchronization

Chapter 6 (a): Synchronization. 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. Background.

galen
Download Presentation

Chapter 6 (a): Synchronization

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Chapter 6 (a): Synchronization

  2. 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

  3. 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

  4. Producer-Consumer • Producer while (true) { /* produce an item and */ /* put in nextProduced */ while (count == BUFFER_SIZE) ; // do nothing b/c full buffer [in] = nextProduced; in = (in + 1) % BUFFER_SIZE; count++; } • Consumer while (true) { while (count == 0) ; // do nothing b/c empty nextConsumed = buffer[out]; out = (out + 1) % BUFFER_SIZE; count--; /* consume the item */ /* in nextConsumed */ }

  5. Race Condition • count++ could be implemented asregister1 = count register1 = register1 + 1 count = register1 • count-- could be implemented asregister2 = 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}

  6. 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

  7. 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 • What happens when two threads execute concurrently? … hits = hits + 1; …

  8. T2 Shared counters • Possible result: lost update! • One other possible result: everything works.  Difficult to debug • Called a “race condition” hits = 0 T1 time read hits (0) read hits (0) hits = 0 + 1 hits = 0 + 1 hits = 1

  9. 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!

  10. Scheduler Assumptions 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 Process b: while(i > -10) i = i - 1; print “B won!”; Process a: while(i < 10) i = i +1; print “A won!”;

  11. 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

  12. 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? CS1 Process 1 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.

  13. Critical-Section Problem • 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

  14. T2 T2 Critical Section Goals • Threads do some stuff but eventually mighttry to access shared data T1 time • CSEnter(); • Critical section • CSExit(); • CSEnter(); • Critical section • CSExit(); T1

  15. T2 T2 Critical Section Goals • Perhaps they loop (perhaps not!) T1 • CSEnter(); • Critical section • CSExit(); • CSEnter(); • Critical section • CSExit(); T1

  16. 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

  17. CSEnter() { while(inside) continue; inside = true; } A first idea: Have a boolean flag, inside. Initially false. Solving the problem 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?

  18. CSEnter(int i) { inside[i] = true; while(inside[j]) continue; } A different idea (assumes just two threads): Have a boolean flag, inside[i]. Initially false. Solving the problem: Take 2 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?

  19. CSEnter(int i) { while(turn != i) continue; } Another broken solution, for two threads Have a turn variable, turn, initially 1. Solving the problem: Take 3 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?

  20. 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?

  21. 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!

  22. 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.

  23. Generalize to N Threads? • Obvious approach won’t work: • Issue: Who’s turn is next? CSExit(int i) { inside[i] = false; } CSEnter(int i) { inside[i] = true; for(J = 0; J < N; J++) while(inside[J] && turn == J) continue; }

  24. 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

  25. 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!

  26. int ticket[n]; Bakery Algorithm: “Take 2” CSEnter(int i) { ticket[i] = max(ticket[0], … ticket[N-1])+1; for(J = 0; J < N; J++) while(ticket[J] && ticket[j] < ticket[i]) continue; } CSExit(int i) { ticket[i] = 0; } Just add 1 to the max! • Clever idea: just add one to the max. • Oops… two could pick the same value!

  27. 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)

  28. Bakery Algorithm: “Take 4” • int ticket[N]; • boolean picking[N] = false; CSEnter(int i) { ticket[i] = max(ticket[0], … ticket[N-1])+1; for(J = 0; J < N; J++) while(ticket[J] && (ticket[J],J) < (ticket[i],i)) continue; } CSExit(int i) { ticket[i] = 0; } • Oops… i could look at J when J is still storing its ticket, and yet J could have a lower id than me (i)!

  29. Bakery Algorithm: Almost final • int ticket[N]; • boolean choosing[N] = false; CSEnter(int i) { choosing[i] = true; 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; } } CSExit(int i) { ticket[i] = 0; }

  30. 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.

  31. Bakery Algorithm: Final • int ticket[N]; /* Important: Disable thread scheduling when changing N */ • boolean choosing[N] = false; CSEnter(int i) { do { 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; } } CSExit(int i) { ticket[i] = 0; }

  32. 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

  33. Critical Section Using Locks

  34. TestAndSet Instruction • Definition: boolean TestAndSet (boolean *target) { boolean rv = *target; *target = TRUE; return rv: }

  35. Solution using TestAndSet • Shared boolean variable lock., initialized to false. • Solution: while (true) { while ( TestAndSet (&lock )) ; /* do nothing // critical section lock = FALSE; // remainder section }

  36. 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 // set lock to 0 RET

  37. Swap Instruction • Definition: void Swap (boolean *a, boolean *b) { boolean temp = *a; *a = *b; *b = temp: }

  38. 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 }

  39. Critical Sections using Swap cs_enter: MOVE REGISTER, #1 // put 1 in register XCHG REGISTER, LOCK // swap reg and lock contents CMP REGISTER, #0 // was lock zero? JNE cs_enter // if not, loop RET // otherwise return, in cs cs_exit: MOVE LOCK, #0 // store a zero in lock RET // return to caller

  40. 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

  41. End of Chapter 6 (a)

More Related