Presentation is loading. Please wait.

Presentation is loading. Please wait.

Operating Systems ECE344 Ashvin Goel ECE University of Toronto Synchronization.

Similar presentations


Presentation on theme: "Operating Systems ECE344 Ashvin Goel ECE University of Toronto Synchronization."— Presentation transcript:

1 Operating Systems ECE344 Ashvin Goel ECE University of Toronto Synchronization

2 Overview  Producer-consumer problem  Monitors  Semaphores  Classic synchronization examples 2

3 Synchronization  Recall that concurrent programming raises two issues  Race conditions o Problem: Certain interleavings cause bad results o Soln: Mutex locks help avoid races by running code atomically  Synchronization o Problem: Threads need to synchronize with each other o Soln: Now we consider how to handle synchronization, using a classic synchronization problem in operating systems 3

4 Producer-Consumer Problem  Threads communicate with each other using a shared buffer of fixed size (i.e., bounded buffer) o One or more producers fill buffer o One or more consumers empty buffer  Two synchronizations conditions o Producers wait if the buffer is full o Consumers wait if the buffer is empty 4

5 Bounded Buffer Implementation  Implementation uses a circular buffer o Producers write at in, increment in, go clockwise o Consumers read from out, increment out, go clockwise o Number of elements in buffer: count = (in - out + n) % n o Buffer is full when it has n-1 elements (why not n elements?)  count == (n - 1) o Buffer is empty when it has no elements  in == out 5 shared variables: char buf[8]; // 7 slots usable int in; // place to write int out; // place to read 7 0 1 2 in = 6 out = 3 buffer has 3 elements

6 Try 1 : Single Producer-Consumer  Is this code correct for a single producer, single consumer? 6 char receive() { while (in == out) { } // empty msg = buf[out]; out = (out + 1) % n; return msg; } void send(char msg) { int count = (in – out + n) % n; while (count == n - 1) { } // full buf[in] = msg; in = (in + 1) % n; }

7 Try 2: Single Producer-Consumer  Is this code correct for a single producer, single consumer?  Is this code correct with multiple producers, multiple consumers? 7 char receive() { while (in == out) { } // empty msg = buf[out]; out = (out + 1) % n; return msg; } void send(char msg) { while ((in–out+n)%n == n - 1) { } // full buf[in] = msg; in = (in + 1) % n; }

8 Try 3 – Use Locking  Let’s try to make this code work for multiple producers and consumers by adding locks  Is this code correct? 8 char receive() { lock(l); while (in == out) { } // empty msg = buf[out]; out = (out + 1) % n; unlock(l); return msg; } void send(char msg) { lock(l); while ((in–out+n)%n == n - 1) { } // full buf[in] = msg; in = (in + 1) % n; unlock(l); }

9 Try 4 – Release Locks Before Spinning  Is this code correct? 9 char receive() { lock(l); while (in == out) { unlock(l); lock(l); } // empty msg = buf[out]; out = (out + 1) % n; unlock(l); return msg; } void send(char msg) { lock(l); while ((in–out+n)%n == n - 1) { unlock(l); lock(l); } // full buf[in] = msg; in = (in + 1) % n; unlock(l); }

10  Is this code correct? o What if we switch unlock() and thread_sleep()? char receive() { lock(l); while (in == out) { unlock(l); thread_sleep(empty); lock(l); } // empty msg = buf[out]; if ((in–out+n)%n == n–1) thread_wakeup(full); out = (out + 1) % n; unlock(l); return msg; } void send(char msg) { lock(l); while ((in–out+n)%n == n - 1) { unlock(l); thread_sleep(full); lock(l); } // full buf[in] = msg; if (in == out) thread_wakeup(empty); in = (in + 1) % n; unlock(l); } Try 5 – Sleep After Unlocking 10

11 Synchronization Challenges  Can’t spin or sleep while holding lock o Causes deadlock  Can’t release lock and then sleep o Causes race because wakeup notification can be lost  Need a way to release the lock and sleep atomically! o Next we see how this can be done using monitors 11

12 Monitors  A structured method for concurrent programming  Mutual exclusion o Any shared data is accessed via methods o All methods acquire lock at the start of their code, release lock at the end of their code o Thus all shared data is accessed in critical section  Synchronization o Methods synchronize with each other using one or more condition variables o These variables allow programs to define arbitrary conditions under which:  A thread goes to sleep (blocks)  A thread wakes up another sleeping thread 12

13 Condition Variable Abstraction  A condition variable is used within monitor methods o cv = cv_create():// create a condition variable o cv_destroy(cv): // destroy a condition variable o cv_wait(cv, lock):  Always wait on some condition until another thread signals it  While thread waits, lock is released atomically  When wait returns, lock is reacquired o cv_signal(cv, lock):  Wakeup one thread waiting on the condition  If a signal occurs before a wait, signal is lost o cv_broadcast(cv, lock):  Wakeup all threads waiting on the condition 13

14 Producer-Consumer with Monitors 14 char receive() { lock(l); while (in == out) { wait(empty, l); } // empty msg = buf[out]; if ((in–out+n)%n == n–1) signal(full, l); out = (out + 1) % n; unlock(l); return msg; } void send(char msg) { lock(l); while ((in–out+n)%n == n - 1) { wait(full, l); } // full buf[in] = msg; if (in == out) signal(empty, l); in = (in + 1) % n; unlock(l); } Global variables: buf[n], in, out; lock l = 0; cv full; // no initialization cv empty;

15 Variable Initialization Using Monitors  Is this code correct? 15 // called by Thread T1 // initially V = NULL Method1() { lock(l); V = malloc(…); // signal that V // is non-NULL signal(cv, l); … unlock(l); } // called by Thread T2 Method2() { lock(l); // wait until V is non-NULL wait(cv, l); assert(V); … unlock(l); }

16 Variable Initialization Using Monitors  Note that wait and signal must be called within lock  What would happen if the lock was not used above? 16 // called by Thread T1 // initially V = NULL Method1() { lock(l); V = malloc(…); // signal that V // is non-NULL signal(cv, l); … unlock(l); } // called by Thread T2 Method2() { lock(l); if (!V) { // wait until V is non-NULL wait(cv, l); } assert(V); … unlock(l); }

17 Semaphores  Semaphores provide an alternate method for synchronization  A semaphore tracks number of available resources using down() and up() operations 17 down(semaphore s) { while (s <= 0) { // wait until resource is available } s = s – 1; // acquire a resource // after down(), s >= 0 } up(semaphore s) { s = s + 1; // make a resource available }

18 Understanding Semaphores  Why must down and up be atomic?  If s is initialized to 1, then o down(s) behaves like lock(s) o up(s) behaves like unlock(s)  The differences between semaphores and locks are based on the intended use o Different threads call down() and up() for synchronization o up() can be called before down(), to “bank” resources 18

19 Synchronizing With Interrupts  Consider a program that reads keys from the keyboard o Program needs to wait until a key is typed, or get the key that has already been typed o Keyboard interrupt handler can buffer one key in key_buf o keyboard_sem tracks nr. of keys available, initialized to 0 19 // on keyboard interrupt void kbd_interrupt_handler(char key) { key_buf = key; up(keyboard_sem); } // program issues read char read_from_keyboard() { down(keyboard_sem); return key_buf; }

20 Producer-Consumer with Semaphores 20 char receive() { down(full); lock(l); msg = buf[out]; out = (out + 1) % n; unlock(l); up(empty); return msg; } void send(char msg) { down(empty); lock(l); buf[in] = msg; in = (in + 1) % n; unlock(l); up(full); } Global variables: buf[n], in, out; lock l; sem full = 0; // no full slots sem empty = n; // all slots are empty

21 Implementing Blocking Semaphores  Previous semaphore implementation used spinning  A blocking semaphore requires interacting with thread scheduler o Using thread_sleep() and thread_wakeup() 21 struct semaphore { int count; …}; down(sem) { while (sem->count <= 0) { thread_sleep(sem); } sem->count--; } up(sem) { sem->count++; thread_wakeup(sem); }

22 Atomic Semaphore Operations  down(s) and up(s) must be atomic  How should they be implemented? o Mutual exclusion (as usual)  Disable interrupts on uniprocessors‏  Use spinlocks on multi-processors  Next, we show the implementation for a uniprocessor 22

23 Implementing down() and up() Atomically  Could we replace the “while” in down() with “if”?  Why does down() disable interrupts before calling thread_sleep()?  Isn’t sleeping while holding lock (interrupt disable) a problem? 23 down(sem) { disable interrupts; while (sem->count <= 0) { thread_sleep(sem); } sem->count--; enable interrupts; } up(sem) { disable interrupts; s->count++; thread_wakeup(sem); enable interrupts; }

24 Classic Synchronization Examples  Dining philosophers  Readers and writers 24

25 The Dining Philosophers Problem  Five philosophers sit at a table  A fork lies between every pair of philosophers  Philosophers (1) think, (2) grab one fork, (3) grab another fork, (4) eat, (5) put down one fork, (6) put the other fork 25

26 Dining Philosophers: Try 1  Assume take_fork and put_fork have locks in them to make them atomic o Is this solution correct? 26 #define N 5 Philosopher() { while(TRUE) { Think(); take_fork(i); take_fork((i+1)% N); Eat(); put_fork(i); put_fork((i+1)% N); } Each philosopher is modeled with a thread

27 Dining Philosophers: Try 2 27 #define N 5 Philosopher() { while(TRUE) { Think(); take_fork(i); take_fork((i+1)% N); Eat(); put_fork(i); put_fork((i+1)% N); } take_forks(i) put_forks(i)

28 Take Forks 28 // test whether philosopher i // can take both forks // call with mutex set – why? test(int i) { if (state[i] == HUNGRY && state[LEFT] != EATING && state[RIGHT] != EATING){ state[i] = EATING; // Signal Philosopher i up(sem[i]); } int state[N]; lock mutex; sem sem[N] = {0}; take_forks(int i) { lock(mutex); state[i] = HUNGRY; test(i); unlock(mutex); down(sem[i]); }

29 Put Forks 29 // test whether philosopher i // can take both forks // call with mutex set test(int i) { if (state[i] == HUNGRY && state[LEFT] != EATING && state[RIGHT] != EATING){ state[i] = EATING; // Signal Philosopher i up(sem[i]); } put_forks(int i) { lock(mutex); state[i] = THINKING; test(LEFT); test(RIGHT); unlock(mutex); } int state[N]; lock mutex; sem sem[N] = {0};

30 The Readers and Writers Problem  Multiple reader and writer threads want to access some shared data  Multiple readers can read concurrently  Writers must synchronize with readers and other writers  Synchronization requirements o Only one writer can write the shared data at a time o When a writer is writing, no readers must access the data  Goals o Maximize concurrency o Prevent starvation 30

31 Readers/Writers - Basics 31 Reader () { rc = rc + 1; // Read shared data rc = rc – 1; // non-critical section } Mutex lock = UNLOCKED; Semaphore data = 1; int rc = 0; Writer () { // non-critical section // Write shared data }

32 Readers/Writers - Mutex 32 Reader () { lock(lock); rc = rc + 1; unlock(lock); // Read shared data lock(lock); rc = rc – 1; unlock(lock); // non-critical section } Mutex lock = UNLOCKED; Semaphore data = 1; int rc = 0; Writer () { // non-critical section // Write shared data }

33 Readers/Writers – Synchronization  Any problems with this solution? 33 Reader () { lock(lock); if (rc == 0) down(data); rc = rc + 1; unlock(lock); // Read shared data lock(lock); rc = rc – 1; if (rc == 0) up(data); unlock(lock); // non-critical section } Mutex lock = UNLOCKED; Semaphore data = 1; int rc = 0; Writer () { // non-critical section down(data); // Write shared data up(data); }

34 Summary  Synchronization enables threads to wait on some condition before proceeding  Producer-consumer problem o Motivates the need for synchronization o Show that implementing synchronization using ad-hoc methods generally leads to incorrect results  Two systematic solutions for implementing synchronization o Monitors o Semaphores 34

35 Think Time  What is the difference between mutual exclusion and synchronization?  Why are locks, by themselves, not sufficient for solving synchronization problems?  How would you solve the producer-consumer problem using interrupt disabling  What are the differences between a monitor and a semaphore?  What are the differences between wait() and down()?  What are the differences between signal() and up()?  Why might you prefer one over the other? 35


Download ppt "Operating Systems ECE344 Ashvin Goel ECE University of Toronto Synchronization."

Similar presentations


Ads by Google