Presentation is loading. Please wait.

Presentation is loading. Please wait.

1 Outline Part 1 Objectives: –To define the process and thread abstractions. –To briefly introduce mechanisms for implementing processes (threads). –To.

Similar presentations


Presentation on theme: "1 Outline Part 1 Objectives: –To define the process and thread abstractions. –To briefly introduce mechanisms for implementing processes (threads). –To."— Presentation transcript:

1 1 Outline Part 1 Objectives: –To define the process and thread abstractions. –To briefly introduce mechanisms for implementing processes (threads). –To introduce the critical section problem. –To learn how to reason about the correctness of concurrent programs. Administrative details: –Groups are listed with “class pics”.Groups –Pictures – make sure name-to-face mapping is correct. –Password protected name: cps110 passwd: OntheGo

2 2 Lab 1 suggestions Some code interleavings can cause “system” problems – e.g. freeing the same object twice –Add code to detect this and give error message. This code should be “conditional” on an argument, so you can see what happens if you let the system handle the problem. Fetching data from a freed object should also be detected and stopped, conditionally.

3 Concurrency Multiple things happening simultaneously –logically or physically Causes –Interrupts –Voluntary context switch (system call/trap) –Shared memory multiprocessor PPPP Memory

4 HW Support for Atomic Operations Could provide direct support in HW –Atomic increment –Insert node into sorted list?? Just provide low level primitives to construct atomic sequences –called synchronization primitives LOCK(counter->lock); // Wait here until unlocked counter->value = counter->value + 1; UNLOCK(counter->lock); test&set (x) instruction: returns previous value of x and sets x to “1” LOCK(x) => while (test&set(x)); UNLOCK(x) => x = 0;

5 5 The Basics of Processes Processes are the OS-provided abstraction of multiple tasks (including user programs) executing concurrently. A Process IS: one instance of a program (which is only a passive set of bits) executing (implying an execution context – register state, memory resources, etc.) OS schedules processes to share CPU.

6 6 Why Use Processes? To capture naturally concurrent activities within the structure of the programmed system. To gain speedup by overlapping activities or exploiting parallel hardware. –From DMA to multiprocessors PPPP Memory

7 7 Separation of Policy and Mechanism (System Design Principle) “Why and What” vs. “How” Objectives and strategies vs. data structures, hardware and software implementation issues. Process abstraction vs. Process machinery Can you think of examples?

8 8 Process Abstraction Unit of scheduling One (or more*) sequential threads of control –program counter, register values, call stack Unit of resource allocation –address space (code and data), open files –sometimes called tasks or jobs Operations on processes: fork (clone-style creation), wait (parent on child), exit (self-termination), signal, kill. Process-related System Calls in Unix.

9 9 Threads and Processes Decouple the resource allocation aspect from the control aspect Thread abstraction - defines a single sequential instruction stream (PC, stack, register values) Process - the resource context serving as a “container” for one or more threads (shared address space) Kernel threads - unit of scheduling (kernel- supported thread operations  still slow)

10 10 Threads and Processes Address Space Thread

11 11 An Example Address Space Thread Editing thread: Responding to your typing in your doc Autosave thread: periodically writes your doc file to disk doc Doc formatting process

12 12 User-Level Threads To avoid the performance penalty of kernel- supported threads, implement at user level and manage by a run-time system –Contained “within” a single kernel entity (process) –Invisible to OS (OS schedules their container, not being aware of the threads themselves or their states). Poor scheduling decisions possible. User-level thread operations can be 100x faster than kernel thread operations, but need better integration / cooperation with OS.

13 13 Context Switching When a process is running, its program counter, register values, stack pointer, etc. are contained in the hardware registers of the CPU. The process has direct control of the CPU hardware for now. When a process is not the one currently running, its current register values are saved in a process descriptor data structure (PCB - process control block) Context switching involves moving state between CPU and various processes’ PCBs by the OS.

14 14 Process State Transitions Ready Create Process Running Blocked Done Wakeup (due to interrupt) sleep (due to outstanding request of syscall) scheduled suspended while another process scheduled

15 15 Process Mechanisms PCB data structure in kernel memory represents a process (allocated on process creation, deallocated on termination). PCBs reside on various state queues (including a different queue for each “cause” of waiting) reflecting the process’s state. As a process executes, the OS moves its PCB from queue to queue (e.g. from the “waiting on I/O” queue to the “ready to run” queue).

16 16 PCBs & Queues process state process identifier PC Stack Pointer (SP) general purpose reg owner userid open files scheduling parameters memory mgt stuff queue ptrs...other stuff... process state process identifier PC Stack Pointer (SP) general purpose reg owner userid open files scheduling parameters memory mgt stuff queue ptrs...other stuff... process state process identifier PC Stack Pointer (SP) general purpose reg owner userid open files scheduling parameters memory mgt stuff queue ptrs...other stuff... head ptr tail ptr head ptr tail ptr Ready QueueWait on Disk Read

17 17 Interleaved Schedules Uni-processor implementation logical concept / multiprocessor implementation context switch

18 18 The Trouble with Concurrency in Threads... Thread0Thread1 Data: x while(i<10) {x  x+1; i++;} 0 while(j<10) {x  x+1; j++;} 0 0 ij What is the value of x when both threads leave this while loop?

19 19 Nondeterminism What unit of work can be performed without interruption? Indivisible or atomic operations. Interleavings - possible execution sequences of operations drawn from all threads. Race condition - final results depend on ordering and may not be “correct”. while (i<10) {x  x+1; i++;} load value of x into reg yield( ) add 1 to reg yield ( ) store reg value at x yield ( )

20 20 Interleaving Thread 0 Load x (value 0) Incr x (value 1 in reg) Store x (value 1) Load x (value 1) for 2 nd iteration Incr x (value 2 in reg) … Store x for 10 th iteration (value 10) Thread 1 Load x (value 0) Incr x (value 1 in reg) Store x (value 1) … Store x for 9 th iteration (value 9) Load x (value 1) for 10 th iteration Incr x (value 2 in reg) Store x (value 2) for 10 th iteration

21 21 Reasoning about Interleavings On a uniprocessor, the possible execution sequences depend on when context switches can occur –Voluntary context switch - the process or thread explicitly yields the CPU (blocking on a system call it makes, or invoking a Yield operation). –Interrupts or exceptions occurring - an asynchronous handler is activated, disrupting the execution flow. –Preemptive scheduling - a timer interrupt may cause an involuntary context switch at any point in the code. On multiprocessors, the ordering of operations on shared memory locations is the important factor. –Memory references are atomic, but instructions aren’t

22 The Trouble with Concurrency Two threads (T1,T2) in one address space or two processes in the kernel One counter ld r2, count add r1, r2, r3 st count, r1 Shared Data count ld r2, count add r1, r2, r3 st count, r1 Time T1T2count ld (count) add switch ld (count) add st (count+1) count+1 switch st (count+1)count+1

23 Solution: Atomic Sequence of Instructions Atomic Sequence –Appears to execute to completion without any intervening operations Time T1T2count begin atomic ld (count) add switch begin atomic st (count+1) count+1 end atomic switch ld (count+1) add st (count+2)count+2 end atomic wait

24 24 Critical Sections If a sequence of non-atomic operations must be executed as if it were atomic in order to be correct, then we need to provide a way to constrain the possible interleavings in this critical section of our code. –Critical sections are code sequences that contribute to “bad” race conditions. –Synchronization is needed around such critical sections. Mutual Exclusion - goal is to ensure that critical sections execute atomically w.r.t. related critical sections in other threads or processes. –How?

25 25 The Critical Section Problem Each process follows this template: while (1) {...other stuff... // processes in here shouldn’t stop others enter_region( ); critical section exit_region( ); } The problem is to define enter_region and exit_region to ensure mutual exclusion with some degree of fairness.

26 26 Implementation Options for Mutual Exclusion Disable Interrupts Busywaiting solutions - spinlocks –execute a tight loop if critical section is busy –benefits from specialized atomic (read-mod-write) instructions Blocking synchronization –sleep (enqueued on wait queue) while C.S. is busy Synchronization primitives (abstractions, such as locks) which are provided by a system may be implemented with some combination of these techniques.

27 27 The Trouble with Concurrency in Threads... Thread0Thread1 Data: x while(i<10) {x  x+1; i++;} 0 while(j<10) {x  x+1; j++;} 0 0 ij What is the value of x when both threads leave this while loop?

28 28 Range of Answers Process 0 LD x // x currently 0 Add 1 ST x // x now 1, stored over 9 Do 9 more full loops // leaving x at 10 Process1 LD x // x currently 0 Add 1 ST x // x now 1 Do 8 more full loops // x = 9 LD x // x now 1 Add 1 ST x // x = 2 stored over 10

29 29 The Critical Section Problem while (1) {...other stuff... enter_region(); critical section exit_region( ); }

30 30 Boolean flag[2]; proc (int i) { while (TRUE){ compute; flag[i] = TRUE; while(flag[(i+1) mod 2]); critical section; flag[i] = FALSE; } flag[0] = flag[1]= FALSE; fork (proc, 0); fork (proc, 1); Is it correct? Assume they go lockstep. Both set their own flag to TRUE. Both busywait forever on the other’s flag -> deadlock. Proposed Algorithm for 2 Process Mutual Exclusion

31 31 Proposed Algorithm for 2 Process Mutual Exclusion enter_region: needin [me] = true; turn = you; while (needin [you] && turn == you) {no_op}; exit_region: needin [me] = false; Is it correct?

32 32 Interleaving of Execution of 2 Threads (blue and green) enter_region: needin [me] = true; turn = you; while (needin [you] && turn == you) {no_op}; Critical Section exit_region: needin [me] = false; enter_region: needin [me] = true; turn = you; while (needin [you] && turn == you) {no_op}; Critical Section exit_region: needin [me] = false;

33 33 needin [blue] = true; needin [green] = true; turn = green; turn = blue; while (needin [green] && turn == green) while (needin [blue] && turn == blue){no_op}; needin [blue] = false; while (needin [blue] && turn == blue) Critical Section needin [green] = false; Critical Section

34 34 needin [blue] = true; needin [green] = true; turn = blue; turn = green; while (needin [green] && turn == green) while (needin [blue] && turn == blue) Critical Section Oooops! Greedy Version (turn = me)

35 35 Synchronization We illustrated the dangers of race conditions when multiple threads execute instructions that interfere with each other when interleaved. Goal in solving the critical section problem is to build synchronization so that the sequence of instructions that can cause a race condition are executed AS IF they were indivisible (just appearances) “Other stuff” can be interleaved with critical section code as well as with the enter_region and exit_region protocols, but it is deemed OK.

36 36 Peterson’s Algorithm for 2 Process Mutual Exclusion enter_region: needin [me] = true; turn = you; while (needin [you] && turn == you) {no_op}; exit_region: needin [me] = false; What about more than 2 processes?

37 37 Can we extend 2-process algorithm to work with n processes?

38 38 Can we extend 2-process algorithm to work with n processes? needin [me] = true; turn = you; needin [me] = true; turn = you; needin [me] = true; turn = you; needin [me] = true; turn = you; needin [me] = true; turn = you; CS Idea:Tournament Details:Bookkeeping (left to the reader)

39 39 Lamport’s Bakery Algorithm enter_region: choosing[me] = true; number[me] = max(number[0:n-1]) + 1; choosing[me] = false; for (j=0; n-1; j++) { { while (choosing[j] != 0) ; while((number[j] != 0 ) and ((number[j] < number[me]) or ((number[j] == number[me]) and (j < me)))) ; } exit_region: number[me] = 0;

40 40 Explanation of Lamport’s Bakery Algorithm choosing[me] = true; number[me] = max(number[0:n-1]) + 1; choosing[me] = false; /* choosing[i] is false when number[i] is not changing to non-zero */ for (j=0; n-1; j++) { { while (choosing[j]) ; while((number[j] != 0 ) and ((number[j] < number[me]) or ((number[j] == number[me]) and (j < me)))) ; } /* While thread i is in this for-loop, number[i] is non-zero; if thread j (j number[i]. Even if thread i has examined j already, i can enter CS(i), and j will NOT enter CS(j) until i leaves CS(i), and sets number[i] to 0. */

41 41 Interleaving / Execution Sequence with Bakery Algorithm Thread 0 Thread 1 Thread 2 Thread 3 Choosing= Number [0]= Number [3]= Number [2]= Number [1]= False 0 0 0 0

42 42 Thread 0 Thread 1 Thread 2 Thread 3 Choosing= Number [0]= Number [3]= Number [2]= Number [1]= True False 0 0 0 0 1 1

43 43 for (j=0; n-1; j++) { { while (choosing[j]) {skip} while((number[j] != 0 ) and ((number[j] < number[me]) or ((number[j] == number[me]) and (j < me)))) {skip} } Thread 0 Thread 1 (running) Thread 2 Thread 3 Choosing= Number [0]= Number [3]= Number [2]= Number [1]= TrueFalse 0 1 1 0 j

44 44 for (j=0; n-1; j++) { { while (choosing[j]) {skip} while((number[j] != 0 ) and ((number[j] < number[me]) or ((number[j] == number[me]) and (j < me)))) {skip} } Thread 0 Thread 1 Thread 2 Thread 3 Choosing= Number [0]= Number [3]= Number [2]= Number [1]= False 2 1 1 0

45 45 for (j=0; n-1; j++) { { while (choosing[j]) {skip} while((number[j] != 0 ) and ((number[j] < number[me]) or ((number[j] == number[me]) and (j < me)))) {skip} } Thread 0 Thread 1 Thread 2 Thread 3 Choosing= Number [0]= Number [3]= Number [2]= Number [1]= False True 2 1 1 3

46 46 for (j=0; n-1; j++) { { while (choosing[j]) {skip} while((number[j] != 0 ) and ((number[j] < number[me]) or ((number[j] == number[me]) and (j < me)))) {skip} } Thread 0 Thread 1 Thread 2 Thread 3 Choosing= Number [0]= Number [3]= Number [2]= Number [1]= False True 2 1 1 3 Thread 3 Stuck

47 47 for (j=0; n-1; j++) { { while (choosing[j]) {skip} while((number[j] != 0 ) and ((number[j] < number[me]) or ((number[j] == number[me]) and (j < me)))) {skip} } Thread 0 Thread 1 Thread 2 Thread 3 Choosing= Number [0]= Number [3]= Number [2]= Number [1]= False True 2 1 0 3

48 48 for (j=0; n-1; j++) { { while (choosing[j]) {skip} while((number[j] != 0 ) and ((number[j] < number[me]) or ((number[j] == number[me]) and (j < me)))) {skip} } Thread 0 Thread 1 Thread 2 Thread 3 Choosing= Number [0]= Number [3]= Number [2]= Number [1]= False 2 1 0 3

49 49 Hardware Assistance Most modern architectures provide some support for building synchronization: atomic read-modify-write instructions. Example: test-and-set (loc, reg) [ sets bit to 1 in the new value of loc; returns old value of loc in reg ] Other examples: compare-and-swap, fetch-and-op [ ] notation means atomic

50 50 Busywaiting with Test-and-Set Declare a shared memory location to represent a busyflag on the critical section we are trying to protect. enter_region (or acquiring the “lock”): waitloop: tset busyflag, R0 // R0  busyflag; busyflag  1 bnz R0, waitloop // was it already set? exit region (or releasing the “lock”): busyflag  0

51 51 Pros and Cons of Busywaiting Key characteristic - the “waiting” process is actively executing instructions in the CPU and using memory cycles. Appropriate when: –High likelihood of finding the critical section unoccupied (don’t need context switch just to find that out) or estimated wait time is very short Disadvantages: –Wastes resources (CPU, memory, bus bandwidth)

52 52 Blocking Synchronization OS implementation involving changing the state of the “waiting” process from running to blocked. Need some synchronization abstraction known to OS - provided by system calls. –mutex locks with operations acquire and release –semaphores with operations P and V (down, up) –condition variables with wait and signal

53 53 Template for Implementing Blocking Synchronization Associated with the lock is a memory location (busy) and a queue for waiting threads/processes. Acquire syscall: while (busy) {enqueue caller on lock’s queue} /*upon waking to nonbusy lock*/ busy = true; Release syscall: busy = false; /* wakeup */ move any waiting threads to Ready queue

54 54 Pros and Cons of Blocking Waiting processes/threads don’t consume CPU cycles Appropriate: when the cost of a system call is justified by expected waiting time –High likelihood of contention for lock –Long critical sections Disadvantage: OS involvement d  overhead

55 55 The Alpha and MIPS 4000 processor architectures have no atomic read-modify-write instructions, i.e., no test-and-set-lock instruction (TS). Atomic update is supported by pairs of load_locked (LDL) and store-conditional (STC) instructions. The semantics of the Alpha architecture’s LDL and STC instructions are as follows. Executing an LDL Rx, y instruction loads the memory at the specified address (y) into the specified general register (Rx), and holds y in a special per-processor lock register. STC Rx, y stores the contents of the specified general register (Rx) to memory at the specified address (y), but only if y matches the address in the CPU’s lock register. If STC succeeds, it places a one in Rx; if it fails, it places a zero in Rx. Several kinds of events can cause the machine to clear the CPU lock register, including traps and interrupts. Moreover, if any CPU in a multiprocessor system successfully completes a STC to address y, then every other processor’s lock register is atomically cleared if it contains the value y. Show how to use LDL and STC to implement safe busy-waiting

56 56 Acquire: LDL R1 flag /*R1  flag*/ BNZ R1 Acquire /* if (R1 != 0) already locked*/ LDI R2 1 STC R2 flag /* try to set flag*/ BEZ R2 Acquire /* if STC failed, retry*/ Release:LDI R2 0 STC R2 flag /* reset lock, breaking “lock” on flag*/ BEZ R2 Error /* should never happen*/

57 57 Outline Part 2 Objectives: –Higher level synchronization mechanisms –Classic problems in concurrency Administrative details:

58 58 Semaphores Well-known synchronization abstraction Defined as a non-negative integer with two atomic operations P(s) - [wait until s > 0; s--] V(s) - [s++] The atomicity and the waiting can be implemented by either busywaiting or blocking solutions.

59 59 Semaphore Usage Binary semaphores can provide mutual exclusion (solution of critical section problem) Counting semaphores can represent a resource with multiple instances (e.g. solving producer/consumer problem) Signaling events (persistent events that stay relevant even if nobody listening right now)

60 60 while (1) {...other stuff... critical section } The Critical Section Problem P(mutex) V(mutex) Semaphore: mutex initially 1 Fill in the boxes

61 61 When is a code section a critical section? Thread 0 a = a + c; b = b + c; Thread 1 a = a + c; b = b + c;

62 62 When is a code section a critical section? Thread 0 P(mutex) a = a + c; b = b + c; V(mutex) Thread 1 P(mutex) a = a + c; b = b + c; V(mutex)

63 63 When is a code section a critical section? Thread 0 P(mutexa) a = a + c; V(mutexa) P(mutexb) b = b + c; V(mutexb) Thread 1 P(mutexa) a = a + c; V(mutexa) P(mutexb) b = b + c; V(mutexb)

64 64 When is a code section a critical section? Thread 0 P(mutex0) a = a + c; b = b + c; V(mutex0) Thread 1 P(mutex1) a = a + c; b = b + c; V(mutex1)

65 65 When is a code section a critical section? Thread 0 a = a + c; b = b + c; Thread 1 a = a + c; b = b + c; Thread 2 c = a + b;

66 66 When is a code section a critical section? Thread 0 P(mutexa) a = a + c; V(mutexa) P(mutexb) b = b + c; V(mutexb) Thread 1 P(mutexa) a = a + c; V(mutexa) P(mutexb) b = b + c; V(mutexb) Thread 2 ? c = a + b; ?

67 67 When is a code section a critical section? Thread 0 P(mutexa) a = a + c; V(mutexa) P(mutexb) b = b + c; V(mutexb) Thread 1 P(mutexa) a = a + c; V(mutexa) P(mutexb) b = b + c; V(mutexb) Thread 2 P(mutexa) P(mutexb) c = a + b; V(mutexa) V(mutexb)

68 68 When is a code section a critical section? Thread 0 P(mutex); a = a + c; b = b + c; V(mutex); Thread 1 P(mutex); a = a + c; b = b + c; V(mutex); Thread 2 P(mutex); c = a + b; V(mutex);

69 69 Classic Problems There are a number of “classic” problems each of which represents a class of synchronization situations Critical Section problem Producer/Consumer problem Reader/Writer problem 5 Dining Philosophers

70 70 Producer / Consumer Producer: while(whatever) {locally generate item fill empty buffer with item } Consumer: while(whatever) { get item from full buffer use item }

71 71 Producer / Consumer (with Counting Semaphores) Producer: while(whatever) {locally generate item fill empty buffer with item } Consumer: while(whatever) { get item from full buffer use item } P(emptybuf); V(fullbuf); P(fullbuf); V(emptybuf); Semaphores: emptybuf initially N; fullbuf initially 0;

72 72 What does it mean that Semaphores have persistence? Tweedledum and Tweedledee Problem Separate threads executing their respective procedures. The code below is intended to cause them to forever take turns exchanging insults through the shared variable X in strict alternation. The Sleep() and Wakeup() routines operate as follows: –Sleep blocks the calling thread, – Wakeup unblocks a specific thread if that thread is blocked, otherwise its behavior is unpredictable (We’ll assume it does nothing)

73 73 The code shown above exhibits a well-known synchronization flaw. Outline a scenario in which this code would fail, and the outcome of that scenario void Tweedledum() { while(1) { Sleep(); x = Quarrel(x); Wakeup(Tweedledee); } void Tweedledee() { while(1) { x = Quarrel(x); Wakeup(Tweedledum); Sleep(); } Lost Wakeup: If dee goes first to sleep, the wakeup is lost (since dum isn’t sleeping yet). Both sleep forever.

74 74 Show how to fix the problem by replacing the Sleep and Wakeup calls with semaphore P (down) and V (up) operations. void Tweedledum() { while(1) { Sleep(); x = Quarrel(x); Wakeup(Tweedledee); } void Tweedledee() { while(1) { x = Quarrel(x); Wakeup(Tweedledum); Sleep(); } P(dum); V(dee); semaphore dee = 0; semaphore dum = 0; V(dum); P(dee):

75 75 Monitor Abstraction Encapsulates shared data and operations with mutual exclusive use of the object (an associated lock). Associated Condition Variables with operations of Wait and Signal. monitor_lock enQdeQ init shared data entry queue notEmpty conditions

76 76 Condition Variables We build the monitor abstraction out of a lock (for the mutual exclusion) and a set of associated condition variables. Wait on condition: releases lock held by caller, caller goes to sleep on condition’s queue. When awakened, it must reacquire lock. Signal condition: wakes up one waiting thread. Broadcast: wakes up all threads waiting on this condition.

77 77 Monitor Abstraction monitor_lock enQdeQ init shared data entry queue notEmpty conditions EnQ :{acquire (lock); if (head == null) {head = item; signal (lock, notEmpty);} else tail->next = item; tail = item; release(lock);} deQ :{acquire (lock); if (head == null) wait (lock, notEmpty); item = head; if (tail == head) tail = null; head=item->next; release(lock);}

78 78 Monitor Abstraction monitor_lock enQdeQ init shared data entry queue notEmpty conditions EnQ :{acquire (lock); if (head == null) {head = item; signal (lock, notEmpty);} else tail->next = item; tail = item; release(lock);} deQ :{acquire (lock); if (head == null) wait (lock, notEmpty); item = head; if (tail == head) tail = null; head=item->next; release(lock);}

79 79 Monitor Abstraction monitor_lock enQdeQ init shared data entry queue notEmpty conditions EnQ :{acquire (lock); if (head == null) {head = item; signal (lock, notEmpty);} else tail->next = item; tail = item; release(lock);} deQ :{acquire (lock); if (head == null) wait (lock, notEmpty); item = head; if (tail == head) tail = null; head=item->next; release(lock);}

80 80 Monitor Abstraction monitor_lock enQdeQ init shared data entry queue notEmpty conditions EnQ :{acquire (lock); if (head == null) {head = item; signal (lock, notEmpty);} else tail->next = item; tail = item; release(lock);} deQ :{acquire (lock); if (head == null) wait (lock, notEmpty); item = head; if (tail == head) tail = null; head=item->next; release(lock);}

81 81 Monitor Abstraction monitor_lock enQdeQ init shared data entry queue notEmpty conditions EnQ :{acquire (lock); if (head == null) {head = item; signal (lock, notEmpty);} else tail->next = item; tail = item; release(lock);} deQ :{acquire (lock); while (head == null) wait (lock, notEmpty); item = head; if (tail == head) tail = null; head=item->next; release(lock);}

82 82 Monitor Abstraction monitor_lock enQdeQ init shared data entry queue notEmpty conditions EnQ :{acquire (lock); if (head == null) {head = item; signal (lock, notEmpty);} else tail->next = item; tail = item; release(lock);} deQ :{acquire (lock); while (head == null) wait (lock, notEmpty); item = head; if (tail == head) tail = null; head=item->next; release(lock);}

83 83 while (1) {...other stuff... acquire (mutex); critical section release (mutex); } The Critical Section Problem // conceptually “inside” monitor

84 84 P&V using Locks & CV (Monitor) lock PV init shared data entry queue nonZero conditions P : {acquire (lock); while (Sval == 0) wait (lock, nonZero); Sval = Sval –1; release(lock);} V : {acquire (lock); Sval = Sval + 1; signal (lock, nonZero); release(lock);} Sval

85 85 Nachos-style Synchronization synch.h, cc Semaphores Semaphore::P Semaphore::V Locks and condition variables Lock::Acquire Lock::Release Condition::Wait (conditionLock) Condition::Signal (conditionLock) Condition::Broadcast (conditionLock)

86 86 Design Decisions / Issues Locking overhead (granularity) Broadcast vs. Signal Nested lock/condition variable problem LOCK a DO LOCK b DO while (not_ready) wait (b, c) //releases b not a END END My advice – correctness first! Unseen in call

87 87 Lock Granularity – how much should one lock protect? 2 46 8 10 3 head tail A B

88 88 2 46 8 10 3 head tail A B Lock Granularity – how much should one lock protect? Concurrency vs. overhead Complexity threatens correctness

89 89 Using Condition Variables while (! required_conditions) wait (m, c); Why we use “while” not “if” – invariant not guaranteed Why use broadcast vs. signal – can arise if we are using one condition queue for many reasons. Waking threads have to sort it out (spurious wakeups). Possibly better to separate into multiple conditions (but more complexity to code).

90 90 Outline for Part 3 Objective: –Classic synchronization problems. Using higher level synchronization abstractions. –Message passing – communication vs. synchronization Administrative details: –Signing up for demos – demo scheduler –Rules for demos: Four groups should sign up to meet with me, two with Varun. Try to demo some labs with each of us. Each member is expected to be prepared to contribute to each demo –Ask your grader where the demo will be held if they don’t post to the newsgroup For me, my office – LSRC D336.

91 91 5 Dining Philosophers Philosopher 0 Philosopher 1 Philosopher 2 Philosopher 3 Philosopher 4 while(food available) {pick up 2 adj. forks; eat; put down forks; think awhile; }

92 92 Template for Philosopher while (food available) { /*pick up forks*/ eat; /*put down forks*/ think awhile; }

93 93 Naive Solution while (food available) { /*pick up forks*/ eat; /*put down forks*/ think awhile; } P(fork[left(me)]); P(fork[right(me)]); V(fork[left(me)]); V(fork[right(me)]); Does this work?

94 94 Simplest Example of Deadlock Thread 0 P(R1) P(R2) V(R1) V(R2) Thread 1 P(R2) P(R1) V(R2) V(R1) Interleaving P(R1) P(R2) P(R1) waits P(R2) waits R1 and R2 initially 1 (binary semaphore)

95 95 Conditions for Deadlock Mutually exclusive use of resources –Binary semaphores R1 and R2 Circular waiting –Thread 0 waits for Thread 1 to V(R2) and Thread 1 waits for Thread 0 to V(R1) Hold and wait –Holding either R1 or R2 while waiting on other No pre-emption –Neither R1 nor R2 are forcibly removed from their respective holding Threads.

96 96 Philosophy 101 (or why 5DP is interesting) How to eat with your Fellows without causing Deadlock. –Circular arguments (the circular wait condition) –Not giving up on firmly held things (no preemption) –Infinite patience with Half-baked schemes ( hold some & wait for more) Why Starvation exists and what we can do about it.

97 97 Dealing with Deadlock It can be prevented by breaking one of the prerequisite conditions: Mutually exclusive use of resources –Example: Allowing shared access to read-only files (readers/writers problem) circular waiting –Example: Define an ordering on resources and acquire them in order hold and wait no pre-emption

98 98 while (food available) { if (me  0) {P(fork[left(me)]); P(fork[right(me)]);} else {(P(fork[right(me)]); P(fork[left(me)]); } eat; V(fork[left(me)]); V(fork[right(me)]); think awhile; } Circular Wait Condition

99 99 Hold and Wait Condition while (food available) {P(mutex); while (forks [me] != 2) {blocking[me] = true; V(mutex); P(sleepy[me]); P(mutex);} forks [leftneighbor(me)] --; forks [rightneighbor(me)]--; V(mutex): eat; P(mutex); forks [leftneighbor(me)] ++; forks [rightneighbor(me)]++; if (blocking[leftneighbor(me)]) {blocking [leftneighbor(me)] = false; V(sleepy[leftneighbor(me)]); } if (blocking[rightneighbor(me)]) {blocking[rightneighbor(me)] = false; V(sleepy[rightneighbor(me)]); } V(mutex); think awhile; }

100 100 Starvation The difference between deadlock and starvation is subtle: –Once a set of processes are deadlocked, there is no future execution sequence that can get them out of it. –In starvation, there does exist some execution sequence that is favorable to the starving process although there is no guarantee it will ever occur. –Rollback and Retry solutions are prone to starvation. –Continuous arrival of higher priority processes is another common starvation situation.

101 101 5DP - Monitor Style Boolean eating [5]; Lock forkMutex; Condition forksAvail; void PickupForks (int i) { forkMutex.Acquire( ); while ( eating[(i-1)%5]  eating[(i+1)%5] ) forksAvail.Wait(&forkMutex); eating[i] = true; forkMutex.Release( ); } void PutdownForks (int i) { forkMutex.Acquire( ); eating[i] = false; forksAvail.Broadcast(&forkMutex); forkMutex.Release( ); }

102 102 What about this? while (food available) {forkMutex.Acquire( ); while (forks [me] != 2) {blocking[me]=true; forkMutex.Release( ); sleep( ); forkMutex.Acquire( );} forks [leftneighbor(me)]--; forks [rightneighbor(me)]--; forkMutex.Release( ): eat; forkMutex.Acquire( ); forks[leftneighbor(me)] ++; forks [rightneighbor(me)]++; if (blocking[leftneighbor(me)] || blocking[rightneighbor(me)]) wakeup ( ); forkMutex.Release( ); think awhile; }

103 103 Readers/Writers Problem Synchronizing access to a file or data record in a database such that any number of threads requesting read-only access are allowed but only one thread requesting write access is allowed, excluding all readers.

104 104 Template for Readers/Writers Reader() {while (true) { read } Writer() {while (true) { write } /*request r access*/ /*release r access*/ /*request w access*/ /*release w access*/

105 105 Template for Readers/Writers Reader() {while (true) { read } Writer() {while (true) { write } fd = open(foo, 0); close(fd); fd = open(foo, 1); close(fd);

106 106 Template for Readers/Writers Reader() {while (true) { read } Writer() {while (true) { write } startRead(); endRead(); startWrite(); endWrite();

107 107 R/W - Monitor Style Boolean busy = false; int numReaders = 0; Lock filesMutex; Condition OKtoWrite, OKtoRead; void startRead () { filesMutex.Acquire( ); while ( busy ) OKtoRead.Wait(&filesMutex); numReaders++; filesMutex.Release( );} void endRead () { filesMutex.Acquire( ); numReaders--; if (numReaders == 0) OKtoWrite.Signal(&filesMutex); filesMutex.Release( );} void startWrite() { filesMutex.Acquire( ); while (busy || numReaders != 0) OKtoWrite.Wait(&filesMutex); busy = true; filesMutex.Release( );} void endWrite() { filesMutex.Acquire( ); busy = false; OKtoRead.Broadcast(&filesMutex); OKtoWrite.Signal(&filesMutex); filesMutex.Release( );}

108 108 Semaphore Solution with Writer Priority int readCount = 0, writeCount = 0; semaphore mutex1 = 1, mutex2 = 1; semaphore readBlock = 1; semaphore writePending = 1; semaphore writeBlock = 1;

109 109 Reader(){ while (TRUE) { other stuff; P(writePending); P(readBlock); P(mutex1); readCount = readCount +1; if(readCount == 1) P(writeBlock); V(mutex1); V(readBlock); V(writePending); access resource; P(mutex1); readCount = readCount -1; if(readCount == 0) V(writeBlock); V(mutex1); }} Writer(){ while(TRUE){ other stuff; P(mutex2); writeCount = writeCount +1; if (writeCount == 1) P(readBlock); V(mutex2); P(writeBlock); access resource; V(writeBlock); P(mutex2); writeCount - writeCount - 1; if (writeCount == 0) V(readBlock); V(mutex2); }}

110 110 Reader(){ while (TRUE) { other stuff; P(writePending); P(readBlock); P(mutex1); readCount = readCount +1; if(readCount == 1) P(writeBlock); V(mutex1); V(readBlock); V(writePending); access resource; P(mutex1); readCount = readCount -1; if(readCount == 0) V(writeBlock); V(mutex1); }} Writer(){ while(TRUE){ other stuff; P(mutex2); writeCount = writeCount +1; if (writeCount == 1) P(readBlock); V(mutex2); P(writeBlock); access resource; V(writeBlock); P(mutex2); writeCount - writeCount - 1; if (writeCount == 0) V(readBlock); V(mutex2); }} Assume the writePending semaphore was omitted. What would happen? Reader1Writer1Reader2

111 111 Reader(){ while (TRUE) { other stuff; P(writePending); P(readBlock); P(mutex1); readCount = readCount +1; if(readCount == 1) P(writeBlock); V(mutex1); V(readBlock); V(writePending); access resource; P(mutex1); readCount = readCount -1; if(readCount == 0) V(writeBlock); V(mutex1); }} Writer(){ while(TRUE){ other stuff; P(mutex2); writeCount = writeCount +1; if (writeCount == 1) P(readBlock); V(mutex2); P(writeBlock); access resource; V(writeBlock); P(mutex2); writeCount - writeCount - 1; if (writeCount == 0) V(readBlock); V(mutex2); }} Assume the writePending semaphore was omitted. What would happen? Reader1Writer1Reader2

112 112 Reader(){ while (TRUE) { other stuff; P(writePending); P(readBlock); P(mutex1); readCount = readCount +1; if(readCount == 1) P(writeBlock); V(mutex1); V(readBlock); V(writePending); access resource; P(mutex1); readCount = readCount -1; if(readCount == 0) V(writeBlock); V(mutex1); }} Writer(){ while(TRUE){ other stuff; P(mutex2); writeCount = writeCount +1; if (writeCount == 1) P(readBlock); V(mutex2); P(writeBlock); access resource; V(writeBlock); P(mutex2); writeCount - writeCount - 1; if (writeCount == 0) V(readBlock); V(mutex2); }} Assume the writePending semaphore was omitted. What would happen? Reader1Writer1Reader2

113 113 Assume the writePending semaphore was omitted in the solution just given. What would happen? This is supposed to give writers priority. However, consider the following sequence: Reader 1 arrives, executes through P(readBlock); Reader 1 executes P(mutex1); Writer 1 arrives, waits at P(readBlock); Reader 2 arrives, waits at P(readBlock); Reader 1 executes V(mutex1); then V(readBlock); Reader 2 may now proceed…wrong

114 114 Practice Larry, Moe, and Curly are planting seeds. Larry digs the holes. Moe then places a seed in each hole. Curly then fills the hole up. There are several synchronization constraints: –Moe cannot plant a seed unless at least one empty hole exists, but Moe does not care how far Larry gets ahead of Moe. –Curly cannot fill a hole unless at least one hole exists in which Moe has planted a seed, but the hole has not yet been filled. Curly does not care how far Moe gets ahead of Curly. –Curly does care that Larry does not get more than MAX holes ahead of Curly. Thus, if there are MAX unfilled holes, Larry has to wait. –There is only one shovel with which both Larry and Curly need to dig and fill the holes, respectively. Sketch out the pseudocode for the 3 threads which represent Larry, Curly, and Moe using whatever synchronization method you like.

115 115 Larry () {while (TRUE) { P(holes); P(shovel); dig; V(shovel); V(empty); } Moe () {while(TRUE){ P(empty); seed empty hole; V(seeded); } semaphore holes = MAX; semaphore shovel = 1; semaphore empty, seeded = 0; Curly () {while(TRUE){ P(seeded); P(shovel); fill hole; V(shovel); V(holes); }

116 116 Nachos Implementation of Semaphores Semaphore::Semaphore(char* debugName, int initialValue) { name = debugName; value = initialValue; queue = new List; } void Semaphore::V() { Thread *thread; IntStatus oldLevel = interrupt->SetLevel(IntOff); thread = (Thread *)queue->Remove(); if (thread != NULL) // make thread ready, consuming the V immediately scheduler->ReadyToRun(thread); value++; (void) interrupt->SetLevel(oldLevel); } void Semaphore::P() { IntStatus oldLevel = interrupt->SetLevel(IntOff); // disable interrupts while (value == 0) { // semaphore not available queue->Append((void *)currentThread); // so go to sleep currentThread->Sleep(); } value--; // semaphore available, // consume its value (void) interrupt->SetLevel(oldLevel);// re-enable interrupts }

117 117 Shared and Private Data Data shared across threads may need critical section protection when modified Data “private” to threads can be used freely Private data: –Register contents (and register variables) –Variables declared local in the procedure fork() starts, and any procedure called by it –Elements of shared arrays used by only one thread Shared data: –Globally declared variables –State variables of shared objects


Download ppt "1 Outline Part 1 Objectives: –To define the process and thread abstractions. –To briefly introduce mechanisms for implementing processes (threads). –To."

Similar presentations


Ads by Google