Download presentation
Presentation is loading. Please wait.
Published byJohnathan Shelton Modified over 8 years ago
1
1 CENG334 Introduction to Operating Systems Erol Sahin Dept of Computer Eng. Middle East Technical University Ankara, TURKEY URL: http://kovan.ceng.metu.edu.tr/~erol/Courses/CENG334 Synchronization primitives Topics Spinlocks Strict Alternation Peterson’s solution Hardware solutions
2
2 Spinlocks Very simple way to implement a lock: Why doesn't this work? Where is the race condition? struct lock { int held = 0; } void acquire(lock) { while (lock->held); lock->held = 1; } void release(lock) { lock->held = 0; } The caller busy waits for the lock to be released Adapted from Matt Welsh’s (Harvard University) slides.
3
3 Implementing Spinlocks Problem is that the internals of the lock acquire/release have critical sections too! The acquire( ) and release( ) actions must be atomic Atomic means that the code cannot be interrupted during execution “All or nothing” execution struct lock { int held = 0; } void acquire(lock) { while (lock->held); lock->held = 1; } void release(lock) { lock->held = 0; } What can happen if there is a context switch here? Adapted from Matt Welsh’s (Harvard University) slides.
4
4 Implementing Spinlocks Problem is that the internals of the lock acquire/release have critical sections too! The acquire( ) and release( ) actions must be atomic Atomic means that the code cannot be interrupted during execution “All or nothing” execution struct lock { int held = 0; } void acquire(lock) { while (lock->held); lock->held = 1; } void release(lock) { lock->held = 0; } This sequence needs to be atomic Adapted from Matt Welsh’s (Harvard University) slides.
5
5 Implementing Spinlocks Problem is that the internals of the lock acquire/release have critical sections too! The acquire( ) and release( ) actions must be atomic Atomic means that the code cannot be interrupted during execution “All or nothing” execution Doing this with help from hardware! Disabling interrupts Why does this prevent a context switch from occurring? Atomic instructions – CPU guarantees entire action will execute atomically Test-and-set Compare-and-swap Doing this in only software! Strict alternation Not an acceptable solution Peterson’s solution Adapted from Matt Welsh’s (Harvard University) slides.
6
6 Spinlocks using test-and-set CPU provides the following as one atomic instruction: So to fix our broken spinlocks, we do this: bool test_and_set(bool *flag) { … // Hardware dependent implementation } struct lock { int held = 0; } void acquire(lock) { while(test_and_set(&lock->held)); } void release(lock) { lock->held = 0; } Adapted from Matt Welsh’s (Harvard University) slides.
7
7 What's wrong with spinlocks? OK, so spinlocks work (if you implement them correctly), and they are simple. So what's the catch? struct lock { int held = 0; } void acquire(lock) { while(test_and_set(&lock->held)); } void release(lock) { lock->held = 0; } Adapted from Matt Welsh’s (Harvard University) slides.
8
8 Problems with spinlocks Horribly wasteful! Threads waiting to acquire locks spin on the CPU Eats up lots of cycles, slows down progress of other threads Note that other threads can still run... how? What happens if you have a lot of threads trying to acquire the lock? Only want spinlocks as primitives to build higher-level synchronization constructs Adapted from Matt Welsh’s (Harvard University) slides.
9
9 Disabling Interrupts An alternative to spinlocks: Can two threads disable/reenable interrupts at the same time? What's wrong with this approach? struct lock { // Note – no state! } void acquire(lock) { cli(); // disable interrupts } void release(lock) { sti(); // reenable interupts } Adapted from Matt Welsh’s (Harvard University) slides.
10
10 Disabling Interrupts An alternative to spinlocks: Can two threads disable/reenable interrupts at the same time? What's wrong with this approach? Can only be implemented at kernel level (why?) Inefficient on a multiprocessor system (why?) All locks in the system are mutually exclusive No separation between different locks for different bank accounts struct lock { // Note – no state! } void acquire(lock) { cli(); // disable interrupts } void release(lock) { sti(); // reenable interupts } Adapted from Matt Welsh’s (Harvard University) slides.
11
11 Software solution 1: Strict Alternation Guarantees mutual exclusion. Does not guarantee progress --- enforces strict alternation of processes entering CS's. Bounded waiting violated --- suppose one process terminates while its its turn? int turn = 0;/*shared variable*/ void *thread1(){ while(1){ while (turn != 0) ;/*busy wait*/ critical_region(); turn = 1; noncritical_region(); } void *thread2(){ while(1){ while (turn != 1) ;/*busy wait*/ critical_region(); turn = 0; noncritical_region(); }
12
12 Software Solution 2: Peterson’s solution #define FALSE 0 #define TRUE 1 #define N 2 int turn;/*shared variable*/ int interested[N]; /* init to all 0’s */ void enter_region(int process){ int other; /* the number of the other process */ other = 1-process; interested[process] = TRUE; turn = process; while(turn==process && interested[other]==TRUE); } void leave_region(int process){ interested[process] = FALSE; }
13
13 Peterson’s solution /* Initially neither process is in the critical region. */ #define FALSE 0 #define TRUE 1 #define N 2 int turn;/*shared variable*/ int interested[N]; /* init to all 0’s */ void enter_region(int process){ /* first called by process 0 */ int other; /* the number of the other process */ other = 1-process; interested[process] = TRUE; /* interested[0] = TRUE interested[1]= FALSE*/ turn = process; /* turn = 0 */ while(turn==process && interested[other]==TRUE); /* returns immediately */ } void leave_region(int process){ interested[process] = FALSE; }
14
14 Peterson’s solution /* Process 1 calls enter_region while process 0 is still in */ #define FALSE 0 #define TRUE 1 #define N 2 int turn;/*shared variable*/ int interested[N]; /* init to all 0’s */ void enter_region(int process){ /* called by process 1 */ int other; /* the number of the other process */ other = 1-process; interested[process] = TRUE; /* interested[0] = TRUE interested[1] = TRUE*/ turn = process; /* turn = 1 */ while(turn==process && interested[other]==TRUE); /* blocks on busy wait */ } /* until process 0 calls leave_region() to set interested[0] = 0 */ void leave_region(int process){ interested[process] = FALSE; }
15
15 Peterson’s solution /* Initially neither process is in the critical region. */ #define FALSE 0 #define TRUE 1 #define N 2 int turn;/*shared variable*/ int interested[N]; /* init to all 0’s */ void enter_region(int process){ /* first called by process 0 and process 1 */ int other; /* the number of the other process */ other = 1-process; interested[process] = TRUE; /* interested[0] = TRUE interested[1]= TRUE */ turn = process; /* race condition: turn = 0 or turn = 1 */ while(turn==process && interested[other]==TRUE); /* returns immediately */ } /* for the process who last set turn. The other remains waiting */ void leave_region(int process){ interested[process] = FALSE; }
16
16 Mutexes – Blocking Locks Really want a thread waiting to enter a critical section to block Put the thread to sleep until it can enter the critical section Frees up the CPU for other threads to run Straightforward to implement using our TCB queues! Lock wait queue Thread 1 unlocked Lock state Ø 1) Check lock state ??? Adapted from Matt Welsh’s (Harvard University) slides.
17
17 Mutexes – Blocking Locks Really want a thread waiting to enter a critical section to block Put the thread to sleep until it can enter the critical section Frees up the CPU for other threads to run Straightforward to implement using our TCB queues! Lock wait queue Thread 1 Ø 1) Check lock state 2) Set state to locked 3) Enter critical section locked Lock state Adapted from Matt Welsh’s (Harvard University) slides.
18
18 Mutexes – Blocking Locks Really want a thread waiting to enter a critical section to block Put the thread to sleep until it can enter the critical section Frees up the CPU for other threads to run Straightforward to implement using our TCB queues! Lock wait queue Thread 1 Ø 1) Check lock state locked Lock state Thread 2 ??? Adapted from Matt Welsh’s (Harvard University) slides.
19
19 Mutexes – Blocking Locks Really want a thread waiting to enter a critical section to block Put the thread to sleep until it can enter the critical section Frees up the CPU for other threads to run Straightforward to implement using our TCB queues! Lock wait queue Thread 1 1) Check lock state locked Lock state Thread 2 2) Add self to wait queue (sleep) Thread 2 Ø Adapted from Matt Welsh’s (Harvard University) slides.
20
20 Mutexes – Blocking Locks Really want a thread waiting to enter a critical section to block Put the thread to sleep until it can enter the critical section Frees up the CPU for other threads to run Straightforward to implement using our TCB queues! Lock wait queue Thread 1 1) Check lock state locked Lock state Thread 3 2) Add self to wait queue (sleep) Thread 2 ??? Thread 3 Adapted from Matt Welsh’s (Harvard University) slides.
21
21 Mutexes – Blocking Locks Really want a thread waiting to enter a critical section to block Put the thread to sleep until it can enter the critical section Frees up the CPU for other threads to run Straightforward to implement using our TCB queues! Lock wait queue Thread 1 1) Thread 1 finishes critical section locked Lock state Thread 2Thread 3 Adapted from Matt Welsh’s (Harvard University) slides.
22
22 Mutexes – Blocking Locks Really want a thread waiting to enter a critical section to block Put the thread to sleep until it can enter the critical section Frees up the CPU for other threads to run Straightforward to implement using our TCB queues! Lock wait queue Thread 1 1) Thread 1 finishes critical section 2) Reset lock state to unlocked Thread 2Thread 3 3) Wake one thread from wait queue unlocked Lock state Thread 3 Adapted from Matt Welsh’s (Harvard University) slides.
23
23 Mutexes – Blocking Locks Really want a thread waiting to enter a critical section to block Put the thread to sleep until it can enter the critical section Frees up the CPU for other threads to run Straightforward to implement using our TCB queues! Lock wait queue Thread 3 can now grab lock and enter critical section Thread 2Thread 3 locked Lock state Adapted from Matt Welsh’s (Harvard University) slides.
24
24 Limitations of locks Locks are great, and simple. What can they not easily accomplish? What if you have a data structure where it's OK for many threads to read the data, but only one thread to write the data? Bank account example. Locks only let one thread access the data structure at a time. Adapted from Matt Welsh’s (Harvard University) slides.
25
25 Limitations of locks Locks are great, and simple. What can they not easily accomplish? What if you have a data structure where it's OK for many threads to read the data, but only one thread to write the data? Bank account example. Locks only let one thread access the data structure at a time. What if you want to protect access to two (or more) data structures at a time? e.g., Transferring money from one bank account to another. Simple approach: Use a separate lock for each. What happens if you have transfer from account A -> account B, at the same time as transfer from account B -> account A? Hmmmmm... tricky. Adapted from Matt Welsh’s (Harvard University) slides.
26
26 CENG334 Introduction to Operating Systems Erol Sahin Dept of Computer Eng. Middle East Technical University Ankara, TURKEY URL: http://kovan.ceng.metu.edu.tr/ceng334 Semaphores Topics: Need for higher-level synchronization primitives Semaphores and their implementation The Producer/Consumer problem and its solution with semaphores The Reader/Writer problem and its solution with semaphores
27
27 Higher-level synchronization primitives We have looked at one synchronization primitive: locks Locks are useful for many things, but sometimes programs have different requirements. Examples? Say we had a shared variable where we wanted any number of threads to read the variable, but only one thread to write it. How would you do this with locks? What's wrong with this code? Reader() { lock.acquire(); mycopy = shared_var; lock.release(); return mycopy; } Writer() { lock.acquire(); shared_var = NEW_VALUE; lock.release(); } Adapted from Matt Welsh’s (Harvard University) slides.
28
28 Semaphores Higher-level synchronization construct Designed by Edsger Dijkstra in the 1960's, part of the THE operating system (classic stuff!) Semaphore is a shared counter Two operations on semaphores: P() or wait() or down() From Dutch “proeberen”, meaning “test” Atomic action: Wait for semaphore value to become > 0, then decrement it V() or signal() or up() From Dutch “verhogen”, meaning “increment” Atomic action: Increments semaphore value by 1. Semaphore Adapted from Matt Welsh’s (Harvard University) slides.
29
29 Semaphore Example Semaphores can be used to implement locks: A semaphore where the counter value is only 0 or 1 is called a binary semaphore. int withdraw(account, amount) { down(my_semaphore); balance = get_balance(account); balance -= amount; put_balance(account, balance); up(my_semaphore); return balance; } critical section Semaphore my_semaphore = 1; // Initialize to nonzero Adapted from Matt Welsh’s (Harvard University) slides.
30
30 Simple Semaphore Implementation What's wrong with this picture??? struct semaphore { int val; threadlist L; // List of threads waiting for semaphore } down(semaphore S): // Wait until > 0 then decrement while (S.val <= 0) { add this thread to S.L; block(this thread); } S.val = S.val -1; return; up(semaphore S): // Increment value and wake up next thread S.val = S.val + 1; if (S.L is nonempty) { remove a thread T from S.L; wakeup(T); } Why is this a while loop and not just an if statement? Adapted from Matt Welsh’s (Harvard University) slides.
31
31 Simple Semaphore Implementation down() and up() must be atomic actions! struct semaphore { int val; threadlist L; // List of threads waiting for semaphore } down(semaphore S): // Wait until > 0 then decrement while (S.val <= 0) { add this thread to S.L; block(this thread); } S.val = S.val -1; return; up(semaphore S): // Increment value and wake up next thread S.val = S.val + 1; if (S.L is nonempty) { remove a thread T from S.L; wakeup(T); } Adapted from Matt Welsh’s (Harvard University) slides.
32
32 Semaphore Implementation How do we ensure that the semaphore implementation is atomic? Adapted from Matt Welsh’s (Harvard University) slides.
33
33 Semaphore Implementation How do we ensure that the semaphore implementation is atomic? One approach: Make them system calls, and ensure only one down() or up() operation can be executed by any process at a time. This effectively puts a lock around the down() and up() operations themselves! Easy to do by disabling interrupts in the down() and up() calls. Another approach: Use hardware support Say your CPU had atomic down and up instructions Adapted from Matt Welsh’s (Harvard University) slides.
34
34 OK, but why are semaphores useful? A binary semaphore (counter is always 0 or 1) is basically a lock. The real value of semaphores becomes apparent when the counter can be initialized to a value other than 0 or 1. Say we initialize a semaphore's counter to 50. What does this mean about down() and up() operations? Adapted from Matt Welsh’s (Harvard University) slides.
35
35 The Producer/Consumer Problem Also called the Bounded Buffer problem. Producer pushes items into the buffer. Consumer pulls items from the buffer. Producer needs to wait when buffer is full. Consumer needs to wait when the buffer is empty. Producer Consumer Mmmm... donuts Adapted from Matt Welsh’s (Harvard University) slides.
36
36 The Producer/Consumer Problem Also called the Bounded Buffer problem. Producer pushes items into the buffer. Consumer pulls items from the buffer. Producer needs to wait when buffer is full. Consumer needs to wait when the buffer is empty. Producer Consumer zzzzz.... Adapted from Matt Welsh’s (Harvard University) slides.
37
37 One implementation... Producer Consumer int count = 0; Producer() { int item; while (TRUE) { item = bake(); if (count == N) sleep(); insert_item(item); count = count + 1; if (count == 1) wakeup(consumer); } Consumer() { int item; while (TRUE) { if (count == 0) sleep(); item = remove_item(); count = count – 1; if (count == N-1) wakeup(producer); eat(item); } What's wrong with this code? What if we context switch right here?? Adapted from Matt Welsh’s (Harvard University) slides.
38
38 A fix using semaphores Producer Consumer Semaphore mutex = 1; Semaphore empty = N; Semaphore full = 0; Producer() { int item; while (TRUE) { item = bake(); down(empty); down(mutex); insert_item(item); up(mutex); up(full); } Consumer() { int item; while (TRUE) { down(full); down(mutex); item = remove_item(); up(mutex); up(empty); eat(item); } Adapted from Matt Welsh’s (Harvard University) slides.
39
39 Reader/Writers Let's go back to the problem at the beginning of lecture. Single shared object Want to allow any number of threads to read simultaneously But, only one thread should be able to write to the object at a time (And, not interfere with any readers...) Semaphore mutex = 1; Semaphore wrt = 1; int readcount = 0; Writer() { down(wrt); do_write(); up(wrt); } Reader() { down(mutex); readcount++; if (readcount == 1) { down(wrt); } up(mutex); do_read(); down(mutex); readcount--; if (readcount == 0) { up(wrt); } up(mutex); } Why the test here?? Adapted from Matt Welsh’s (Harvard University) slides. ✔ A Reader should only wait for a Writer to complete its do_write(). ✔ A Reader should not wait for other Readers to complete their do_read(). ✔ The Writer should wait for the other Writers to complete their do_write(). ✔ The Writer should wait for all the Readers to complete their do_read().
40
40 Issues with Semaphores Much of the power of semaphores derives from calls to down() and up() that are unmatched See previous example! Unlike locks, acquire() and release() are not always paired. This means it is a lot easier to get into trouble with semaphores. “More rope” Would be nice if we had some clean, well-defined language support for synchronization... Java does! Adapted from Matt Welsh’s (Harvard University) slides.
41
41 CENG334 Introduction to Operating Systems Erol Sahin Dept of Computer Eng. Middle East Technical University Ankara, TURKEY URL: http://kovan.ceng.metu.edu.tr/ceng334 Monitors, Condition variabless Topics: Monitors Condition Variables
42
42 Readers/Writers Problem 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 R R R W
43
43 Reader/Writers Problem Single shared object Want to allow any number of threads to read simultaneously But, only one thread should be able to write to the object at a time (And, not interfere with any readers...) Conditions: A Reader should only wait for a Writer to complete its do_write(). A Reader should not wait for other Readers to complete their do_read(). The Writer should wait for the other Writers to complete their do_write(). The Writer should wait for all the Readers to complete their do_read(). Basic structure of a solution Reader() Wait until no writers Access data base Check out – wake up a waiting writer Writer() Wait until no active readers or writers Access database Check out – wake up waiting readers or writer Adapted from Matt Welsh’s (Harvard University) slides.
44
44 Readers-Writers Problem One issue we need to settle, to clarify problem statement. Suppose that a writer is active and a mixture of readers and writers now shows up. Who should get in next? Or suppose that a writer is waiting and an endless of stream of readers keeps showing up. Is it fair for them to become active? We’ll favor a kind of back-and-forth form of fairness: Once a reader is waiting, readers will get in next. If a writer is waiting, one writer will get in next.
45
45 Reader/Writers Semaphore mutex = 1; Semaphore wrt = 1; int readcount = 0; Writer() { down(wrt); do_write(); up(wrt); } Reader() { down(mutex); readcount++; if (readcount == 1) { down(wrt); } up(mutex); do_read(); down(mutex); readcount--; if (readcount == 0) { up(wrt); } up(mutex); } Why the test here?? Adapted from Matt Welsh’s (Harvard University) slides.
46
46 Issues with Semaphores Much of the power of semaphores derives from calls to down() and up() that are unmatched See previous example! Unlike locks, acquire() and release() are not always paired. This means it is a lot easier to get into trouble with semaphores. “More rope” Would be nice if we had some clean, well-defined language support for synchronization... Java does! Adapted from Matt Welsh’s (Harvard University) slides.
47
47 Monitors A high-level abstraction that provides a convenient and effective mechanism for process synchronization Only one process may be active within the monitor at a time
48
48 Monitor with Condition Variables condition x, y; Two operations on a condition variable: x.wait () – a process that invokes the operation is suspended. x.signal () – resumes one of processes (if any) that invoked x.wait ()
49
49 Java Synchronization Support Java synchronization based on locks and condition variables Every Java object can be used as a lock What's nice about this? Compiler ensures that lock is released before leaving the synchronized block Even if there is an exception!! Condition variable (CV): allows a thread to wait or wakeup other threads Three operations on condition variables: wait() -- Block until another thread calls notify() or notifyAll() on the CV notify() or signal -- Wake up one thread waiting on the CV notifyAll() -- Wake up all threads waiting on the CV Object foo; synchronized (foo) { // Do some stuff with 'foo' locked... } Adapted from Matt Welsh’s (Harvard University) slides.
50
50 Bounded Buffer using CV's int theArray[ARRAY_SIZE], size; Object theLock; void put(int val) { synchronized(theLock) { while (size == ARRAY_SIZE) { theLock.wait(); } addItemToArray(val); size++; if (size == 1) theLock.notify(); } int get() { int item; synchronized(theLock) { while (size == 0) { theLock.wait(); } item = getItemFromArray() size--; if (size == ARRAY_SIZE-1) theLock.notify(); } return item; } Problems with this code?? Adapted from Matt Welsh’s (Harvard University) slides.
51
51 Bounded Buffer using CV's int theArray[ARRAY_SIZE], size; Object theLock; void put(int val) { synchronized(theLock) { while (size == ARRAY_SIZE) { theLock.wait(); } addItemToArray(val); size++; if (size == 1) theLock.notify(); } int get() { int item; synchronized(theLock) { while (size == 0) { theLock.wait(); } item = getItemFromArray() size--; if (size == ARRAY_SIZE-1) theLock.notify(); } return item; } Assumes only a single thread calling put() and get() at a time! If two threads call get(), then two threads call put(), only one will be woken up!! Adapted from Matt Welsh’s (Harvard University) slides.
52
52 How to fix this problem? Every notifyAll() will cause all threads to wake up and re-check the empty or full condition. Could be inefficient if a lot of threads are involved... int theArray[ARRAY_SIZE], size; Object theLock; void put(int val) { synchronized(theLock) { while (size == ARRAY_SIZE) { theLock.wait(); } addItemToArray(val); size++; if (size == 1) theLock.notifyAll(); } int get() { int item; synchronized(theLock) { while (size == 0) { theLock.wait(); } item = getItemFromArray() size--; if (size == ARRAY_SIZE-1) theLock.notifyAll(); } return item; } Adapted from Matt Welsh’s (Harvard University) slides.
53
53 Monitors This style of using locks and CV's to protect access to a shared object is often called a monitor Think of a monitor as a lock protecting an object, plus a queue of waiting threads. Shared data Methods accessing shared data Waiting threads At most one thread in the monitor at a time How is this different than a lock??? Adapted from Matt Welsh’s (Harvard University) slides.
54
54 Monitors Shared data Methods accessing shared data unlocked Adapted from Matt Welsh’s (Harvard University) slides.
55
55 Monitors Shared data Methods accessing shared data locked zzzz... Sleeping thread no longer “in” the monitor. (But not on the waiting queue either! Why?) Adapted from Matt Welsh’s (Harvard University) slides.
56
56 Monitors Shared data Methods accessing shared data locked Monitor stays locked! (Lock now owned by different thread...) zzzz... notify() Adapted from Matt Welsh’s (Harvard University) slides.
57
57 Monitors Shared data Methods accessing shared data locked notify() Adapted from Matt Welsh’s (Harvard University) slides.
58
58 Monitors Shared data Methods accessing shared data locked No guarantee which order threads get into the monitor. (Not necessarily FIFO!) Adapted from Matt Welsh’s (Harvard University) slides.
59
59 CVs in Java In Java, a thread can only call wait(), notify(), or notifyAll() if it holds the lock of the associated object Why? Object foo; /* NOT ALLOWED!!! */ foo.notify(); /* This is ok... */ synchronized(foo) { foo.notify(); } Adapted from Matt Welsh’s (Harvard University) slides.
60
60 CVs in Java In Java, a thread can only call wait(), notify(), or notifyAll() if it holds the lock of the associated object Why? Want to avoid common race conditions! Object foo; /* NOT ALLOWED!!! */ foo.notify(); /* This is ok... */ synchronized(foo) { foo.notify(); } if (foo.count < 0) { synchronized (foo) { foo.wait(); } /* Another thread... */ synchronized (foo) { foo.count++; foo.notify(); } Adapted from Matt Welsh’s (Harvard University) slides.
61
61 Hoare vs. Mesa Monitor Semantics The monitor notify() operation can have two different meanings: Hoare monitors (1974) notify(CV) means to run the waiting thread immediately Causes notifying thread to block Mesa monitors (Xerox PARC, 1980) notify(CV) puts waiting thread back onto the “ready queue” for the monitor But, notifying thread keeps running Adapted from Matt Welsh’s (Harvard University) slides.
62
62 Hoare vs. Mesa Monitor Semantics The monitor notify() operation can have two different meanings: Hoare monitors (1974) notify(CV) means to run the waiting thread immediately Causes notifying thread to block Mesa monitors (Xerox PARC, 1980) notify(CV) puts waiting thread back onto the “ready queue” for the monitor But, notifying thread keeps running What's the practical difference? In Hoare-style semantics, the “condition” that triggered the notify() will always be true when the awoken thread runs For example, that the buffer is now no longer empty In Mesa-style semantics, awoken thread has to recheck the condition Since another thread might have beaten it to the punch Adapted from Matt Welsh’s (Harvard University) slides.
63
63 Hoare vs. Mesa monitors Need to be careful about precise definition of signal and wait. while (n==0) { wait(not_empty); // If nothing, sleep } item = getItemFromArray(); // Get next item Why didn’t we do this? if (n==0) { wait(not_empty); // If nothing, sleep } removeItemFromArray(val);// Get next item Answer: depends on the type of scheduling Hoare-style (most textbooks): Signaler gives lock, CPU to waiter; waiter runs immediately Waiter gives up lock, processor back to signaler when it exits critical section or if it waits again Mesa-style (Java, most real operating systems): Signaler keeps lock and processor Waiter placed on ready queue with no special priority Practically, need to check condition again after wait
64
64 Revisit: Readers/Writers Problem Correctness Constraints: Readers can access database when no writers Writers can access database when no readers or writers Only one thread manipulates state variables at a time State variables (Protected by a lock called “lock”): int NReaders: Number of active readers; initially = 0 int WaitingReaders: Number of waiting readers; initially = 0 int NWriters: Number of active writers; initially = 0 int WaitingWriters: Number of waiting writers; initially = 0 Condition canRead = NIL Conditioin canWrite = NIL
65
65 Readers and Writers Monitor ReadersNWriters { int WaitingWriters, WaitingReaders,NReaders, NWriters; Condition CanRead, CanWrite; Void BeginWrite() { if(NWriters == 1 || NReaders > 0) { ++WaitingWriters; wait(CanWrite); --WaitingWriters; } NWriters = 1; } Void EndWrite() { NWriters = 0; if(WaitingReaders) Signal(CanRead); else Signal(CanWrite); } Void BeginRead() { if(NWriters == 1 || WaitingWriters > 0) { ++WaitingReaders; Wait(CanRead); --WaitingReaders; } ++NReaders; Signal(CanRead); } Void EndRead() { if(--NReaders == 0) Signal(CanWrite); }
66
66 Readers and Writers Monitor ReadersNWriters { int WaitingWriters, WaitingReaders,NReaders, NWriters; Condition CanRead, CanWrite; Void BeginWrite() { if(NWriters == 1 || NReaders > 0) { ++WaitingWriters; wait(CanWrite); --WaitingWriters; } NWriters = 1; } Void EndWrite() { NWriters = 0; if(WaitingReaders) Signal(CanRead); else Signal(CanWrite); } Void BeginRead() { if(NWriters == 1 || WaitingWriters > 0) { ++WaitingReaders; Wait(CanRead); --WaitingReaders; } ++NReaders; Signal(CanRead); } Void EndRead() { if(--NReaders == 0) Signal(CanWrite); }
67
67 Readers and Writers Monitor ReadersNWriters { int WaitingWriters, WaitingReaders,NReaders, NWriters; Condition CanRead, CanWrite; Void BeginWrite() { if(NWriters == 1 || NReaders > 0) { ++WaitingWriters; wait(CanWrite); --WaitingWriters; } NWriters = 1; } Void EndWrite() { NWriters = 0; if(WaitingReaders) notify(CanRead); else notify(CanWrite); } Void BeginRead() { if(NWriters == 1 || WaitingWriters > 0) { ++WaitingReaders; Wait(CanRead); --WaitingReaders; } ++NReaders; Signal(CanRead); } Void EndRead() { if(--NReaders == 0) notify(CanWrite); }
68
68 Readers and Writers Monitor ReadersNWriters { int WaitingWriters, WaitingReaders,NReaders, NWriters; Condition CanRead, CanWrite; Void BeginWrite() { if(NWriters == 1 || NReaders > 0) { ++WaitingWriters; wait(CanWrite); --WaitingWriters; } NWriters = 1; } Void EndWrite() { NWriters = 0; if(WaitingReaders) notify(CanRead); else notify(CanWrite); } Void BeginRead() { if(NWriters == 1 || WaitingWriters > 0) { ++WaitingReaders; Wait(CanRead); --WaitingReaders; } ++NReaders; notify(CanRead); } Void EndRead() { if(--NReaders == 0) notify(CanWrite); }
69
69 Understanding the Solution A writer can enter if there are no other active writers and no readers are waiting
70
70 Readers and Writers Monitor ReadersNWriters { int WaitingWriters, WaitingReaders,NReaders, NWriters; Condition CanRead, CanWrite; Void BeginWrite() { if(NWriters == 1 || NReaders > 0) { ++WaitingWriters; wait(CanWrite); --WaitingWriters; } NWriters = 1; } Void EndWrite() { NWriters = 0; if(WaitingReaders) notify(CanRead); else notify(CanWrite); } Void BeginRead() { if(NWriters == 1 || WaitingWriters > 0) { ++WaitingReaders; Wait(CanRead); --WaitingReaders; } ++NReaders; notify(CanRead); } Void EndRead() { if(--NReaders == 0) notify(CanWrite); }
71
71 Understanding the Solution A reader can enter if There are no writers active or waiting So we can have many readers active all at once Otherwise, a reader waits (maybe many do)
72
72 Readers and Writers Monitor ReadersNWriters { int WaitingWriters, WaitingReaders,NReaders, NWriters; Condition CanRead, CanWrite; Void BeginWrite() { if(NWriters == 1 || NReaders > 0) { ++WaitingWriters; wait(CanWrite); --WaitingWriters; } NWriters = 1; } Void EndWrite() { NWriters = 0; if(WaitingReaders) notify(CanRead); else notify(CanWrite); } Void BeginRead() { if(NWriters == 1 || WaitingWriters > 0) { ++WaitingReaders; Wait(CanRead); --WaitingReaders; } ++NReaders; notify(CanRead); } Void EndRead() { if(--NReaders == 0) notify(CanWrite); }
73
73 Understanding the Solution When a writer finishes, it checks to see if any readers are waiting If so, it lets one of them enter That one will let the next one enter, etc… Similarly, when a reader finishes, if it was the last reader, it lets a writer in (if any is there)
74
74 Readers and Writers Monitor ReadersNWriters { int WaitingWriters, WaitingReaders,NReaders, NWriters; Condition CanRead, CanWrite; Void BeginWrite() { if(NWriters == 1 || NReaders > 0) { ++WaitingWriters; wait(CanWrite); --WaitingWriters; } NWriters = 1; } Void EndWrite() { NWriters = 0; if(WaitingReaders) notify(CanRead); else notify(CanWrite); } Void BeginRead() { if(NWriters == 1 || WaitingWriters > 0) { ++WaitingReaders; Wait(CanRead); --WaitingReaders; } ++NReaders; notify(CanRead); } Void EndRead() { if(--NReaders == 0) notify(CanWrite); }
75
75 Understanding the Solution It wants to be fair If a writer is waiting, readers queue up If a reader (or another writer) is active or waiting, writers queue up … this is mostly fair, although once it lets a reader in, it lets ALL waiting readers in all at once, even if some showed up “after” other waiting writers
76
76 The Big Picture The point here is that getting synchronization right is hard How to pick between locks, semaphores, condvars, monitors??? Locks are very simple for many cases. Issues: Maybe not the most efficient solution For example, can't allow multiple readers but one writer inside a standard lock. Condition variables allow threads to sleep while holding a lock Just be sure you understand whether they use Mesa or Hoare semantics! Semaphores provide pretty general functionality But also make it really easy to botch things up. Java captures a lot of useful common operations with its use of monitors (compiler checking is nice too) But, not possible to implement everything directly with Java's primitives. Adapted from Matt Welsh’s (Harvard University) slides.
Similar presentations
© 2024 SlidePlayer.com. Inc.
All rights reserved.