Upload
bernice-jackson
View
229
Download
7
Embed Size (px)
Citation preview
Operating SystemsECE344
Ashvin GoelECE
University of Toronto
Synchronization
Overview
Producer-consumer problem
Monitors
Semaphores
Classic synchronization examples
2
Synchronization
Recall that concurrent programming raises two issues
Race conditionso Problem: Certain interleavings cause bad resultso Soln: Mutex locks help avoid races by running code
atomically
Synchronizationo Problem: Threads need to synchronize with each othero Soln: Now we consider how to handle synchronization, using
a classic synchronization problem in operating systems
3
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 buffero One or more consumers empty buffer
Two synchronizations conditionso Producers wait if the buffer is fullo Consumers wait if the buffer is empty
4
Bounded Buffer Implementation
Implementation uses a circular buffero Producers write at in, increment in, go clockwiseo Consumers read from out, increment out, go clockwiseo Number of elements in buffer: count = (in - out + n) % no 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 usableint in; // place to writeint out; // place to read
7 0
1
2
in = 6
out = 3buffer has 3 elements
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;}
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;}
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);}
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);}
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
Synchronization Challenges
Can’t spin or sleep while holding locko Causes deadlock
Can’t release lock and then sleepo 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
Monitors
A structured method for concurrent programming
Mutual exclusiono Any shared data is accessed via methodso All methods acquire lock at the start of their code, release
lock at the end of their codeo Thus all shared data is accessed in critical section
Synchronizationo Methods synchronize with each other using one or more
condition variableso These variables allow programs to define arbitrary conditions
under which: A thread goes to sleep (blocks) A thread wakes up another sleeping thread
12
Condition Variable Abstraction
A condition variable is used within monitor methodso cv = cv_create(): // create a condition variableo cv_destroy(cv): // destroy a condition variableo 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
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 initializationcv empty;
Variable Initialization Using Monitors
Is this code correct?
15
// called by Thread T1// initially V = NULLMethod1() { lock(l); V = malloc(…); // signal that V // is non-NULL signal(cv, l); … unlock(l);}
// called by Thread T2Method2() { lock(l); // wait until V is non-NULL wait(cv, l);
assert(V); …
unlock(l);}
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 = NULLMethod1() { lock(l); V = malloc(…); // signal that V // is non-NULL signal(cv, l); … unlock(l);}
// called by Thread T2Method2() { lock(l); if (!V) { // wait until V is non-NULL wait(cv, l); } assert(V);
… unlock(l);}
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}
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 useo Different threads call down() and up() for synchronizationo up() can be called before down(), to “bank” resources
18
19
Synchronizing With Interrupts
Consider a program that reads keys from the keyboardo Program needs to wait until a key is typed, or get the key that
has already been typedo Keyboard interrupt handler can buffer one key in key_bufo keyboard_sem tracks nr. of keys available, initialized to 0
// on keyboard interruptvoidkbd_interrupt_handler(char key){ key_buf = key;
up(keyboard_sem);}
// program issues readcharread_from_keyboard(){
down(keyboard_sem); return key_buf;
}
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 slotssem empty = n; // all slots are empty
Implementing Blocking Semaphores
Previous semaphore implementation used spinning
A blocking semaphore requires interacting with thread schedulero 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);}
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
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;}
Classic Synchronization Examples
Dining philosophers
Readers and writers
24
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
Dining Philosophers: Try 1
Assume take_fork and put_fork have locks in them to make them atomico 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 ismodeled with a thread
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)
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]);}
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};
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 requirementso Only one writer can write the shared data at a timeo When a writer is writing, no readers must access the data
Goalso Maximize concurrencyo Prevent starvation
30
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
}
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
}
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);}
Summary
Synchronization enables threads to wait on some condition before proceeding
Producer-consumer problemo Motivates the need for synchronization o Show that implementing synchronization using ad-hoc
methods generally leads to incorrect results
Two systematic solutions for implementing synchronizationo Monitorso Semaphores
34
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