Operating Systems ECE344 Ashvin Goel ECE University of Toronto Synchronization.

35
Operating Systems ECE344 Ashvin Goel ECE University of Toronto Synchronization

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

Page 1: Operating Systems ECE344 Ashvin Goel ECE University of Toronto Synchronization.

Operating SystemsECE344

Ashvin GoelECE

University of Toronto

Synchronization

Page 2: Operating Systems ECE344 Ashvin Goel ECE University of Toronto Synchronization.

Overview

Producer-consumer problem

Monitors

Semaphores

Classic synchronization examples

2

Page 3: Operating Systems ECE344 Ashvin Goel ECE University of Toronto Synchronization.

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

Page 4: Operating Systems ECE344 Ashvin Goel ECE University of Toronto Synchronization.

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

Page 5: Operating Systems ECE344 Ashvin Goel ECE University of Toronto Synchronization.

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

Page 6: Operating Systems ECE344 Ashvin Goel ECE University of Toronto Synchronization.

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;}

Page 7: Operating Systems ECE344 Ashvin Goel ECE University of Toronto Synchronization.

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;}

Page 8: Operating Systems ECE344 Ashvin Goel ECE University of Toronto Synchronization.

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);}

Page 9: Operating Systems ECE344 Ashvin Goel ECE University of Toronto Synchronization.

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);}

Page 10: Operating Systems ECE344 Ashvin Goel ECE University of Toronto Synchronization.

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

Page 11: Operating Systems ECE344 Ashvin Goel ECE University of Toronto Synchronization.

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

Page 12: Operating Systems ECE344 Ashvin Goel ECE University of Toronto Synchronization.

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

Page 13: Operating Systems ECE344 Ashvin Goel ECE University of Toronto Synchronization.

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

Page 14: Operating Systems ECE344 Ashvin Goel ECE University of Toronto Synchronization.

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;

Page 15: Operating Systems ECE344 Ashvin Goel ECE University of Toronto Synchronization.

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);}

Page 16: Operating Systems ECE344 Ashvin Goel ECE University of Toronto Synchronization.

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);}

Page 17: Operating Systems ECE344 Ashvin Goel ECE University of Toronto Synchronization.

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}

Page 18: Operating Systems ECE344 Ashvin Goel ECE University of Toronto Synchronization.

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

Page 19: Operating Systems ECE344 Ashvin Goel ECE University of Toronto Synchronization.

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;

}

Page 20: Operating Systems ECE344 Ashvin Goel ECE University of Toronto Synchronization.

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

Page 21: Operating Systems ECE344 Ashvin Goel ECE University of Toronto Synchronization.

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);}

Page 22: Operating Systems ECE344 Ashvin Goel ECE University of Toronto Synchronization.

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

Page 23: Operating Systems ECE344 Ashvin Goel ECE University of Toronto Synchronization.

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;}

Page 24: Operating Systems ECE344 Ashvin Goel ECE University of Toronto Synchronization.

Classic Synchronization Examples

Dining philosophers

Readers and writers

24

Page 25: Operating Systems ECE344 Ashvin Goel ECE University of Toronto Synchronization.

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

Page 26: Operating Systems ECE344 Ashvin Goel ECE University of Toronto Synchronization.

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

Page 27: Operating Systems ECE344 Ashvin Goel ECE University of Toronto Synchronization.

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)

Page 28: Operating Systems ECE344 Ashvin Goel ECE University of Toronto Synchronization.

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]);}

Page 29: Operating Systems ECE344 Ashvin Goel ECE University of Toronto Synchronization.

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};

Page 30: Operating Systems ECE344 Ashvin Goel ECE University of Toronto Synchronization.

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

Page 31: Operating Systems ECE344 Ashvin Goel ECE University of Toronto Synchronization.

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

}

Page 32: Operating Systems ECE344 Ashvin Goel ECE University of Toronto Synchronization.

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

}

Page 33: Operating Systems ECE344 Ashvin Goel ECE University of Toronto Synchronization.

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);}

Page 34: Operating Systems ECE344 Ashvin Goel ECE University of Toronto Synchronization.

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

Page 35: Operating Systems ECE344 Ashvin Goel ECE University of Toronto Synchronization.

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