Operating Systems ECE344 Ashvin Goel ECE University of Toronto Synchronization.

Post on 17-Jan-2016

231 views 7 download

Transcript of Operating Systems ECE344 Ashvin Goel ECE University of Toronto Synchronization.

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