30. Condition Variables
Operating System: Three Easy Pieces
1
Concurrency/Sychronization Objectives
 Mutual exclusion (e.g., A and B don’t run a critical section
at the same time) - solved with locks
 Ordering (e.g., B runs/continues execution after A does
something). Example:
 A parent thread might wish to check whether a child thread has
completed. - This is often called a join().
 Producer/Consumer threads may wish to check whether a condition
is true before continuing their job/execution.
2
Ordering Example: Join
3
1 void *child(void *arg) {
2 printf("childn");
3 // XXX how to indicate we are done?
4 return NULL;
5 }
6
7 int main(int argc, char *argv[]) {
8 printf("parent: beginn");
9 pthread_t c;
10 Pthread_create(&c, NULL, child, NULL);
11 // XXX how to wait for child?
12 printf("parent: endn");
13 return 0;
14 }
parent: begin
child
parent: end
A Parent Waiting For Its Child
What we would like to see here is:
how to implement join()?
how to implement exit()?
Parent waiting for child: Spin-based Approach
 This is hugely inefficient as the parent spins and wastes CPU time.
4
1 volatile int done = 0;
2
3 void *child(void *arg) {
4 printf("childn");
5 done = 1;
6 return NULL;
7 }
8
9 int main(int argc, char *argv[]) {
10 printf("parent: beginn");
11 pthread_t c;
12 Pthread_create(&c, NULL, child, NULL);
13 while (done == 0)
14 ; // spin
15 printf("parent: endn");
16 return 0;
17 }
Condition Variables
 Need a new synchronization primitive: Condition Variables
 A Condition Variable (CV) is an explicit queue that threads
can put themselves on when waiting on some condition
- wait(&CV, …)
 Another thread that makes the condition true can signal
the CV to wake up a waiting thread
- signal(&CV, …)
 Different and more powerful than the simple park/unpark()
mechanism
5
Definition and Routines
 Declare a condition variable with static initialization (dynamic possible).
 Operations:
pthread_cond_signal(pthread_cond_t *c);
 wakes a single waiting thread (if >= 1 thread is waiting)
 if there is no waiting thread, just return, doing nothing
pthread_cond_wait(pthread_cond_t *c, pthread_mutex_t *m)
 The wait() call takes a mutex as a parameter and assumes the lock is held
when wait() is called
 puts caller to sleep + releases the lock (atomically)
 When the thread wakes up, it must re-acquire the lock before returning.
6
Pthread_cond_t c= PTHREAD_COND_INITIALIZER;
Join Implementation: Attempt 1
void thread_exit() {
Cond_signal(&c);
}
void thread_join() {
Cond_wait(&c);
}
Parent: Child:
Will it Work?
No. If child runs before parent calls joins, it will sleep forever
Join Implementation: Attempt 2
void thread_join() {
Mutex_lock(&m); // w
if (done == 0) // x
Cond_wait(&c, &m); // y
Mutex_unlock(&m); // z
}
Parent: x (y wont be
executed)
Child: a b
Fixes previous broken ordering:
void thread_exit() {
done = 1; // a
Cond_signal(&c); // b
}
void thread_join() {
if (done == 0) // x
Cond_wait(&c); // y
}
Parent: Child:
Rule of Thumb 1
 Need to Keep state in addition to CV’s!
 CV’s are used to signal threads when state changes
 If state is already as needed, thread shouldn’t wait for a signal!
Parent: x y
Child: a b
… sleep forever …
Can you construct ordering that does not work?
Join Implementation: Attempt 2
void thread_join() {
Mutex_lock(&m); // w
if (done == 0) // x
Cond_wait(&c, &m); // y
Mutex_unlock(&m); // z
}
void thread_exit() {
done = 1; // a
Cond_signal(&c); // b
}
void thread_join() {
if (done == 0) // x
Cond_wait(&c ); // y
}
Parent: Child:
Join Implementation: Attempt 3 using locks
void thread_join() {
Mutex_lock(&m); // w
if (done == 0) // x
Cond_wait(&c, &m); // y
Mutex_unlock(&m); // z
}
void thread_exit() {
Mutex_lock(&m); // a
done =1; // b
Cond_signal(&c); // c
Mutex_unlock(&m);// d
}
void thread_join() {
Mutex_lock(&m); // w
if (done == 0) // x
Cond_wait(&c); // y
Mutex_unlock(&m); // z
}
Parent: Child:
• Fixes previous problem but the parent should relea
se lock before calling wait otherwise child can not
change the state and signal.
• Releasing the lock before wait() will bring the earlie
r issue’s en-countered with Queue lock
Join Implementation: Correct one!
void thread_join() {
Mutex_lock(&m); // w
if (done == 0) // x
Cond_wait(&c, &m); // y
Mutex_unlock(&m); // z
}
void thread_exit() {
Mutex_lock(&m); // a
done =1; // b
Cond_signal(&c); // c
Mutex_unlock(&m);// d
}
void thread_join() {
Mutex_lock(&m); // w
if (done == 0) // x
Cond_wait(&c, &m); // y
Mutex_unlock(&m); // z
}
Parent: Child:
• Therefore, the wait() operation on condition variable
also takes the lock as another argument and it releas
es the lock before putting thread to sleep, so lock i
s available for child thread to signal.
• when awoken, it reacquires the lock before returning
• Rule of thumb 2: Always do wait/signal with lock he
Parent waiting for Child: Use a condition variable
13
1 int done = 0;
2 pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
3 pthread_cond_t c = PTHREAD_COND_INITIALIZER;
4
5 void thr_exit() {
6 Pthread_mutex_lock(&m);
7 done = 1;
8 Pthread_cond_signal(&c);
9 Pthread_mutex_unlock(&m);
10 }
11
12 void *child(void *arg) {
13 printf("childn");
14 thr_exit();
15 return NULL;
16 }
17
18 void thr_join() {
19 Pthread_mutex_lock(&m);
20 while (done == 0)
21 Pthread_cond_wait(&c, &m);
22 Pthread_mutex_unlock(&m);
23 }
Parent waiting for Child: Use a condition variable
14
(cont.)
25 int main(int argc, char *argv[]) {
26 printf("parent: beginn");
27 pthread_t p;
28 Pthread_create(&p, NULL, child, NULL);
29 thr_join();
30 printf("parent: endn");
31 return 0;
32 }
The Producer / Consumer (Bounded Buffer) Problem
 Producer
 Produce data items
 Wish to place data items in a buffer
 Consumer
 Grab data items out of the buffer consume them in some way
 Example: Multi-threaded web server
 Client (producer) thread puts HTTP requests in to a work queue
 Server (Consumer) threads take requests out of this queue for processing
 When you pipe the output of one program into another.
 Example: grep foo file.txt | wc –l
 The grep process is the producer and wc process is the consumer.
 Between them is an in-kernel bounded buffer
15
Solution
 General strategy use condition variables to:
 make producers wait when buffers are full
 make consumers wait when there is nothing to consume
 We will start with easy case:
 1 producer thread
 1 consumer thread
 1 shared buffer slot to fill/consume (i.e max_size = 1)
16
Producer/Consumer Threads
 Producer puts an integer into the shared buffer loops number of times.
 Consumer gets the data out of that shared buffer.
 Try yourself writing the synchronized code…
 Let Numfull = number of buffer slots currently filled
17
Youjip Won
1 void *producer(void *arg) {
2 int i;
3 int loops = (int) arg;
4 for (i = 0; i < loops; i++) {
5 do_fill(i);
6 }
7 }
8 void *consumer(void *arg) {
10 int i;
11 while (1) {
12 int tmp = do_get();
13 printf("%dn", tmp);
14 }
15 }
void *consumer(void *arg) {
while(1) {
Mutex_lock(&m);
if(numfull == 0)
Cond_wait(&cond, &m
);
int tmp = do_get();
Cond_signal(&cond);
Mutex_unlock(&m);
printf(“%dn”, tmp);
}
}
[RUNNABLE] [RUNNING]
numfull=0
void *producer(void *arg) {
for (int i=0; i<loops; i++) {
Mutex_lock(&m);
if(numfull == max)
Cond_wait(&cond, &m
);
do_fill(i);
Cond_signal(&cond);
Mutex_unlock(&m);
}
}
[RUNNABLE] [RUNNING]
numfull=0
void *consumer(void *arg) {
while(1) {
Mutex_lock(&m);
if(numfull == 0)
Cond_wait(&cond, &m
);
int tmp = do_get();
Cond_signal(&cond);
Mutex_unlock(&m);
printf(“%dn”, tmp);
}
}
void *producer(void *arg) {
for (int i=0; i<loops; i++) {
Mutex_lock(&m);
if(numfull == max)
Cond_wait(&cond, &m
);
do_fill(i);
Cond_signal(&cond);
Mutex_unlock(&m);
}
}
[RUNNABLE] [RUNNING]
numfull=0
void *consumer(void *arg) {
while(1) {
Mutex_lock(&m);
if(numfull == 0)
Cond_wait(&cond, &m
);
int tmp = do_get();
Cond_signal(&cond);
Mutex_unlock(&m);
printf(“%dn”, tmp);
}
}
void *producer(void *arg) {
for (int i=0; i<loops; i++) {
Mutex_lock(&m);
if(numfull == max)
Cond_wait(&cond, &m
);
do_fill(i);
Cond_signal(&cond);
Mutex_unlock(&m);
}
}
[RUNNABLE] [RUNNING]
numfull=0
void *consumer(void *arg) {
while(1) {
Mutex_lock(&m);
if(numfull == 0)
Cond_wait(&cond, &m
);
int tmp = do_get();
Cond_signal(&cond);
Mutex_unlock(&m);
printf(“%dn”, tmp);
}
}
void *producer(void *arg) {
for (int i=0; i<loops; i++) {
Mutex_lock(&m);
if(numfull == max)
Cond_wait(&cond, &m
);
do_fill(i);
Cond_signal(&cond);
Mutex_unlock(&m);
}
}
void *consumer(void *arg) {
while(1) {
Mutex_lock(&m);
if(numfull == 0)
Cond_wait(&cond, &m
);
int tmp = do_get();
Cond_signal(&cond);
Mutex_unlock(&m);
printf(“%dn”, tmp);
}
}
[RUNNABLE] [SLEEPING]
numfull=0
void *producer(void *arg) {
for (int i=0; i<loops; i++) {
Mutex_lock(&m);
if(numfull == max)
Cond_wait(&cond, &m
);
do_fill(i);
Cond_signal(&cond);
Mutex_unlock(&m);
}
}
void *consumer(void *arg) {
while(1) {
Mutex_lock(&m);
if(numfull == 0)
Cond_wait(&cond, &m
);
int tmp = do_get();
Cond_signal(&cond);
Mutex_unlock(&m);
printf(“%dn”, tmp);
}
}
[RUNNING] [SLEEPING]
numfull=0
void *producer(void *arg) {
for (int i=0; i<loops; i++) {
Mutex_lock(&m);
if(numfull == max)
Cond_wait(&cond, &m
);
do_fill(i);
Cond_signal(&cond);
Mutex_unlock(&m);
}
}
void *consumer(void *arg) {
while(1) {
Mutex_lock(&m);
if(numfull == 0)
Cond_wait(&cond, &m
);
int tmp = do_get();
Cond_signal(&cond);
Mutex_unlock(&m);
printf(“%dn”, tmp);
}
}
[RUNNING] [SLEEPING]
numfull=0
void *producer(void *arg) {
for (int i=0; i<loops; i++) {
Mutex_lock(&m);
if(numfull == max)
Cond_wait(&cond, &m
);
do_fill(i);
Cond_signal(&cond);
Mutex_unlock(&m);
}
}
void *consumer(void *arg) {
while(1) {
Mutex_lock(&m);
if(numfull == 0)
Cond_wait(&cond, &m
);
int tmp = do_get();
Cond_signal(&cond);
Mutex_unlock(&m);
printf(“%dn”, tmp);
}
}
[RUNNING] [SLEEPING]
numfull=0
void *producer(void *arg) {
for (int i=0; i<loops; i++) {
Mutex_lock(&m);
if(numfull == max)
Cond_wait(&cond, &m
);
do_fill(i);
Cond_signal(&cond);
Mutex_unlock(&m);
}
}
void *consumer(void *arg) {
while(1) {
Mutex_lock(&m);
if(numfull == 0)
Cond_wait(&cond, &m
);
int tmp = do_get();
Cond_signal(&cond);
Mutex_unlock(&m);
printf(“%dn”, tmp);
}
}
[RUNNING] [SLEEPING]
numfull=0
void *producer(void *arg) {
for (int i=0; i<loops; i++) {
Mutex_lock(&m);
if(numfull == max)
Cond_wait(&cond, &m
);
do_fill(i);
Cond_signal(&cond);
Mutex_unlock(&m);
}
}
void *consumer(void *arg) {
while(1) {
Mutex_lock(&m);
if(numfull == 0)
Cond_wait(&cond, &m
);
int tmp = do_get();
Cond_signal(&cond);
Mutex_unlock(&m);
printf(“%dn”, tmp);
}
}
[RUNNING] [SLEEPING]
numfull=1
void *producer(void *arg) {
for (int i=0; i<loops; i++) {
Mutex_lock(&m);
if(numfull == max)
Cond_wait(&cond, &m
);
do_fill(i);
Cond_signal(&cond);
Mutex_unlock(&m);
}
}
void *consumer(void *arg) {
while(1) {
Mutex_lock(&m);
if(numfull == 0)
Cond_wait(&cond, &m
);
int tmp = do_get();
Cond_signal(&cond);
Mutex_unlock(&m);
printf(“%dn”, tmp);
}
}
[RUNNING] [RUNNABLE]
numfull=1
void *producer(void *arg) {
for (int i=0; i<loops; i++) {
Mutex_lock(&m);
if(numfull == max)
Cond_wait(&cond, &m
);
do_fill(i);
Cond_signal(&cond);
Mutex_unlock(&m);
}
}
void *consumer(void *arg) {
while(1) {
Mutex_lock(&m);
if(numfull == 0)
Cond_wait(&cond, &m
);
int tmp = do_get();
Cond_signal(&cond);
Mutex_unlock(&m);
printf(“%dn”, tmp);
}
}
[RUNNING] [RUNNABLE]
numfull=1
void *producer(void *arg) {
for (int i=0; i<loops; i++) {
Mutex_lock(&m);
if(numfull == max)
Cond_wait(&cond, &m
);
do_fill(i);
Cond_signal(&cond);
Mutex_unlock(&m);
}
}
void *consumer(void *arg) {
while(1) {
Mutex_lock(&m);
if(numfull == 0)
Cond_wait(&cond, &m
);
int tmp = do_get();
Cond_signal(&cond);
Mutex_unlock(&m);
printf(“%dn”, tmp);
}
}
[RUNNING] [RUNNABLE]
numfull=1
void *producer(void *arg) {
for (int i=0; i<loops; i++) {
Mutex_lock(&m);
if(numfull == max)
Cond_wait(&cond, &m
);
do_fill(i);
Cond_signal(&cond);
Mutex_unlock(&m);
}
}
void *consumer(void *arg) {
while(1) {
Mutex_lock(&m);
if(numfull == 0)
Cond_wait(&cond, &m
);
int tmp = do_get();
Cond_signal(&cond);
Mutex_unlock(&m);
printf(“%dn”, tmp);
}
}
[RUNNING] [RUNNABLE]
numfull=1
void *producer(void *arg) {
for (int i=0; i<loops; i++) {
Mutex_lock(&m);
if(numfull == max)
Cond_wait(&cond, &m
);
do_fill(i);
Cond_signal(&cond);
Mutex_unlock(&m);
}
}
void *consumer(void *arg) {
while(1) {
Mutex_lock(&m);
if(numfull == 0)
Cond_wait(&cond, &m
);
int tmp = do_get();
Cond_signal(&cond);
Mutex_unlock(&m);
printf(“%dn”, tmp);
}
}
[RUNNING] [RUNNABLE]
numfull=1
void *producer(void *arg) {
for (int i=0; i<loops; i++) {
Mutex_lock(&m);
if(numfull == max)
Cond_wait(&cond, &m
);
do_fill(i);
Cond_signal(&cond);
Mutex_unlock(&m);
}
}
void *producer(void *arg) {
for (int i=0; i<loops; i++) {
Mutex_lock(&m);
if(numfull == max)
Cond_wait(&cond, &m
);
do_fill(i);
Cond_signal(&cond);
Mutex_unlock(&m);
}
}
void *consumer(void *arg) {
while(1) {
Mutex_lock(&m);
if(numfull == 0)
Cond_wait(&cond, &m
);
int tmp = do_get();
Cond_signal(&cond);
Mutex_unlock(&m);
printf(“%dn”, tmp);
}
}
[SLEEPING] [RUNNABLE]
numfull=1
void *consumer(void *arg) {
while(1) {
Mutex_lock(&m);
if(numfull == 0)
Cond_wait(&cond, &m
);
int tmp = do_get();
Cond_signal(&cond);
Mutex_unlock(&m);
printf(“%dn”, tmp);
}
}
void *producer(void *arg) {
for (int i=0; i<loops; i++) {
Mutex_lock(&m);
if(numfull == max)
Cond_wait(&cond, &m
);
do_fill(i);
Cond_signal(&cond);
Mutex_unlock(&m);
}
}
[SLEEPING] [RUNNING]
numfull=1
void *producer(void *arg) {
for (int i=0; i<loops; i++) {
Mutex_lock(&m);
if(numfull == max)
Cond_wait(&cond, &m
);
do_fill(i);
Cond_signal(&cond);
Mutex_unlock(&m);
}
}
[SLEEPING]
numfull=1
[RUNNING]
void *consumer(void *arg) {
while(1) {
Mutex_lock(&m);
if(numfull == 0)
Cond_wait(&cond, &m
);
int tmp = do_get();
Cond_signal(&cond);
Mutex_unlock(&m);
printf(“%dn”, tmp);
}
}
void *producer(void *arg) {
for (int i=0; i<loops; i++) {
Mutex_lock(&m);
if(numfull == max)
Cond_wait(&cond, &m
);
do_fill(i);
Cond_signal(&cond);
Mutex_unlock(&m);
}
}
[SLEEPING]
numfull=0
[RUNNING]
void *consumer(void *arg) {
while(1) {
Mutex_lock(&m);
if(numfull == 0)
Cond_wait(&cond, &m
);
int tmp = do_get();
Cond_signal(&cond);
Mutex_unlock(&m);
printf(“%dn”, tmp);
}
}
void *producer(void *arg) {
for (int i=0; i<loops; i++) {
Mutex_lock(&m);
if(numfull == max)
Cond_wait(&cond, &m
);
do_fill(i);
Cond_signal(&cond);
Mutex_unlock(&m);
}
}
[RUNNABLE]
numfull=0
[RUNNING]
void *consumer(void *arg) {
while(1) {
Mutex_lock(&m);
if(numfull == 0)
Cond_wait(&cond, &m
);
int tmp = do_get();
Cond_signal(&cond);
Mutex_unlock(&m);
printf(“%dn”, tmp);
}
}
void *producer(void *arg) {
for (int i=0; i<loops; i++) {
Mutex_lock(&m);
if(numfull == max)
Cond_wait(&cond, &m
);
do_fill(i);
Cond_signal(&cond);
Mutex_unlock(&m);
}
}
numfull=0
[RUNNING]
[RUNNABLE]
void *consumer(void *arg) {
while(1) {
Mutex_lock(&m);
if(numfull == 0)
Cond_wait(&cond, &m
);
int tmp = do_get();
Cond_signal(&cond);
Mutex_unlock(&m);
printf(“%dn”, tmp);
}
}
void *producer(void *arg) {
for (int i=0; i<loops; i++) {
Mutex_lock(&m);
if(numfull == max)
Cond_wait(&cond, &m
);
do_fill(i);
Cond_signal(&cond);
Mutex_unlock(&m);
}
}
numfull=0
[RUNNING]
[RUNNABLE]
void *consumer(void *arg) {
while(1) {
Mutex_lock(&m);
if(numfull == 0)
Cond_wait(&cond, &m
);
int tmp = do_get();
Cond_signal(&cond);
Mutex_unlock(&m);
printf(“%dn”, tmp);
}
}
void *producer(void *arg) {
for (int i=0; i<loops; i++) {
Mutex_lock(&m);
if(numfull == max)
Cond_wait(&cond, &m
);
do_fill(i);
Cond_signal(&cond);
Mutex_unlock(&m);
}
}
numfull=0
[RUNNING]
[RUNNABLE]
void *consumer(void *arg) {
while(1) {
Mutex_lock(&m);
if(numfull == 0)
Cond_wait(&cond, &m
);
int tmp = do_get();
Cond_signal(&cond);
Mutex_unlock(&m);
printf(“%dn”, tmp);
}
}
void *producer(void *arg) {
for (int i=0; i<loops; i++) {
Mutex_lock(&m);
if(numfull == max)
Cond_wait(&cond, &m
);
do_fill(i);
Cond_signal(&cond);
Mutex_unlock(&m);
}
}
numfull=0
[RUNNING]
[RUNNABLE]
void *consumer(void *arg) {
while(1) {
Mutex_lock(&m);
if(numfull == 0)
Cond_wait(&cond, &m
);
int tmp = do_get();
Cond_signal(&cond);
Mutex_unlock(&m);
printf(“%dn”, tmp);
}
}
void *producer(void *arg) {
for (int i=0; i<loops; i++) {
Mutex_lock(&m);
if(numfull == max)
Cond_wait(&cond, &m
);
do_fill(i);
Cond_signal(&cond);
Mutex_unlock(&m);
}
}
numfull=0
[RUNNING]
[RUNNABLE]
void *consumer(void *arg) {
while(1) {
Mutex_lock(&m);
if(numfull == 0)
Cond_wait(&cond, &m
);
int tmp = do_get();
Cond_signal(&cond);
Mutex_unlock(&m);
printf(“%dn”, tmp);
}
}
void *producer(void *arg) {
for (int i=0; i<loops; i++) {
Mutex_lock(&m);
if(numfull == max)
Cond_wait(&cond, &m
);
do_fill(i);
Cond_signal(&cond);
Mutex_unlock(&m);
}
}
void *consumer(void *arg) {
while(1) {
Mutex_lock(&m);
if(numfull == 0)
Cond_wait(&cond, &m
);
int tmp = do_get();
Cond_signal(&cond);
Mutex_unlock(&m);
printf(“%dn”, tmp);
}
}
numfull=0
[SLEEPING]
[RUNNABLE]
void *consumer(void *arg) {
while(1) {
Mutex_lock(&m);
if(numfull == 0)
Cond_wait(&cond, &m
);
int tmp = do_get();
Cond_signal(&cond);
Mutex_unlock(&m);
printf(“%dn”, tmp);
}
}
void *producer(void *arg) {
for (int i=0; i<loops; i++) {
Mutex_lock(&m);
if(numfull == max)
Cond_wait(&cond, &m
);
do_fill(i);
Cond_signal(&cond);
Mutex_unlock(&m);
}
}
numfull=0
[SLEEPING]
[RUNNING]
void *consumer(void *arg) {
while(1) {
Mutex_lock(&m);
if(numfull == 0)
Cond_wait(&cond, &m
);
int tmp = do_get();
Cond_signal(&cond);
Mutex_unlock(&m);
printf(“%dn”, tmp);
}
}
numfull=0
[SLEEPING]
[RUNNING]
void *producer(void *arg) {
for (int i=0; i<loops; i++) {
Mutex_lock(&m);
if(numfull == max)
Cond_wait(&cond, &m
);
do_fill(i);
Cond_signal(&cond);
Mutex_unlock(&m);
}
}
void *consumer(void *arg) {
while(1) {
Mutex_lock(&m);
if(numfull == 0)
Cond_wait(&cond, &m
);
int tmp = do_get();
Cond_signal(&cond);
Mutex_unlock(&m);
printf(“%dn”, tmp);
}
}
numfull=1
[SLEEPING]
[RUNNING]
void *producer(void *arg) {
for (int i=0; i<loops; i++) {
Mutex_lock(&m);
if(numfull == max)
Cond_wait(&cond, &m
);
do_fill(i);
Cond_signal(&cond);
Mutex_unlock(&m);
}
}
void *consumer(void *arg) {
while(1) {
Mutex_lock(&m);
if(numfull == 0)
Cond_wait(&cond, &m
);
int tmp = do_get();
Cond_signal(&cond);
Mutex_unlock(&m);
printf(“%dn”, tmp);
}
}
numfull=1
[RUNNABLE]
[RUNNING]
void *producer(void *arg) {
for (int i=0; i<loops; i++) {
Mutex_lock(&m);
if(numfull == max)
Cond_wait(&cond, &m
);
do_fill(i);
Cond_signal(&cond);
Mutex_unlock(&m);
}
}
With just a single producer and consumer the code
works fine!
What about 2 consumers?
Can you find a problematic timeline with 2 consumers (still 1 producer)?
void *consumer(void *arg) {
while(1) {
Mutex_lock(&m); //c1
if(numfull == 0) //c2
Cond_wait(&cond, &m); /
/c3
int tmp = do_get(); //c4
Cond_signal(&cond); //c5
Mutex_unlock(&m); //c6
printf(“%dn”, tmp); //c7
}
}
void *producer(void *arg) {
for (int i=0; i<loops; i++) {
Mutex_lock(&m);//p1
if(numfull == max)//p2
Cond_wait(&cond, &m); //
p3
do_fill(i); //p4
Cond_signal(&cond); //p5
Mutex_unlock(&m); //p6
}
}
Rule of Thumb 3
 Whenever a lock is acquired, recheck assumptions
about state! i.e use While loops (not if) with CV’s.
 Possible for another thread to grab lock in between
signal and wakeup from wait
 Sometimes you don’t need to recheck the condition
but it is always safe to do so;
 just do it and be happy!
The code still has a bug!
Can you find another problematic timeline with 2 consumers (still 1 producer)?
void *consumer(void *arg) {
while(1) {
Mutex_lock(&m); //c1
while(numfull == 0) //c2
Cond_wait(&cond, &m); /
/c3
int tmp = do_get(); //c4
Cond_signal(&cond); //c5
Mutex_unlock(&m); //c6
printf(“%dn”, tmp); //c7
}
}
void *producer(void *arg) {
for (int i=0; i<loops; i++) {
Mutex_lock(&m);//p1
while(numfull == max)//p2
Cond_wait(&cond, &m); //
p3
do_fill(i); //p4
Cond_signal(&cond); //p5
Mutex_unlock(&m); //p6
}
}
How to wake the right thread?
 One solution: wake all the threads!
broadcast(cond_t *cv)
- wakes all waiting threads (if >= 1 thread is waiting)
- if there are no waiting thread, just return, doing nothing
 Example use: multi-threaded memory allocation lib
void *allocate(int size) {
mutex_lock(&m);
while (bytesLeft < size)
cond_wait(&c);
… }
void free(void *ptr, int size) {
…
cond_broadcast(&c)
…
}
Any disadvantage? Should it be used for producer/consumer problem?
Producer/Consumer: Two CVs
Is this correct? Can you find a bad schedule?
Correct!
- no concurrent access to shared state
- every time lock is acquired, assumptions are reevaluated
- a consumer will get to run after every do_fill()
- a producer will get to run after every do_get()
void *consumer(void *arg) {
while(1) {
Mutex_lock(&m); //c1
while(numfull == 0) //c2
Cond_wait(&fill, &m); //c
3
int tmp = do_get(); //c4
Cond_signal(&empty); //c5
Mutex_unlock(&m); //c6
printf(“%dn”, tmp); //c7
}
}
void *producer(void *arg) {
for (int i=0; i<loops; i++) {
Mutex_lock(&m);//p1
while(numfull == max)//p2
Cond_wait(&empty, &m); //
p3
do_fill(i); //p4
Cond_signal(&fill); //p5
Mutex_unlock(&m); //p6
}
}
Summary: rules of thumb for CVs
 Keep state in addition to CV’s
 Always do wait/signal with lock held
 Whenever thread wakes from waiting, recheck state
Circular buffer with N elements
 Add more buffer slots  More concurrency and efficiency.
 enables multiple values to be produced or consumed.
 Reduce context switches.
54
1 int buffer[MAX];
2 int in = 0;
3 int out = 0;
4 int count = 0;
5
6 void do_fill(int value) {
7 buffer[in] = value;
8 in = (in + 1) % MAX;
9 count++;
10 }
11
12 int do_get() {
13 int tmp = buffer[out];
14 out = (out + 1) % MAX;
15 count--;
16 return tmp;
17 }
The do_fill( ) and do_get( ) Routines
The Final Producer/Consumer Solution (Cont.)
55
1 cond_t empty, fill;
2 mutex_t mutex;
4 void *producer(void *arg) {
5 int i;
6 for (i = 0; i < loops; i++) {
7 Pthread_mutex_lock(&mutex); // p1
8 while (count == MAX) // p2
9 Pthread_cond_wait(&empty, &mutex); // p3
10 do_fill(i); // p4
11 Pthread_cond_signal(&fill); // p5
12 Pthread_mutex_unlock(&mutex); // p6
13 }
14 }
15
16 void *consumer(void *arg) {
17 int i;
18 for (i = 0; i < loops; i++) {
19 Pthread_mutex_lock(&mutex); // c1
20 while (count == 0) // c2
21 Pthread_cond_wait(&fill, &mutex); // c3
22 int tmp = do_get(); // c4
23 Pthread_cond_signal(&empty); // c5
24 Pthread_mutex_unlock(&mutex); // c6
25 printf("%dn", tmp);
26 }
27 }

semaphre and condition variable in Operating system

  • 1.
    30. Condition Variables OperatingSystem: Three Easy Pieces 1
  • 2.
    Concurrency/Sychronization Objectives  Mutualexclusion (e.g., A and B don’t run a critical section at the same time) - solved with locks  Ordering (e.g., B runs/continues execution after A does something). Example:  A parent thread might wish to check whether a child thread has completed. - This is often called a join().  Producer/Consumer threads may wish to check whether a condition is true before continuing their job/execution. 2
  • 3.
    Ordering Example: Join 3 1void *child(void *arg) { 2 printf("childn"); 3 // XXX how to indicate we are done? 4 return NULL; 5 } 6 7 int main(int argc, char *argv[]) { 8 printf("parent: beginn"); 9 pthread_t c; 10 Pthread_create(&c, NULL, child, NULL); 11 // XXX how to wait for child? 12 printf("parent: endn"); 13 return 0; 14 } parent: begin child parent: end A Parent Waiting For Its Child What we would like to see here is: how to implement join()? how to implement exit()?
  • 4.
    Parent waiting forchild: Spin-based Approach  This is hugely inefficient as the parent spins and wastes CPU time. 4 1 volatile int done = 0; 2 3 void *child(void *arg) { 4 printf("childn"); 5 done = 1; 6 return NULL; 7 } 8 9 int main(int argc, char *argv[]) { 10 printf("parent: beginn"); 11 pthread_t c; 12 Pthread_create(&c, NULL, child, NULL); 13 while (done == 0) 14 ; // spin 15 printf("parent: endn"); 16 return 0; 17 }
  • 5.
    Condition Variables  Needa new synchronization primitive: Condition Variables  A Condition Variable (CV) is an explicit queue that threads can put themselves on when waiting on some condition - wait(&CV, …)  Another thread that makes the condition true can signal the CV to wake up a waiting thread - signal(&CV, …)  Different and more powerful than the simple park/unpark() mechanism 5
  • 6.
    Definition and Routines Declare a condition variable with static initialization (dynamic possible).  Operations: pthread_cond_signal(pthread_cond_t *c);  wakes a single waiting thread (if >= 1 thread is waiting)  if there is no waiting thread, just return, doing nothing pthread_cond_wait(pthread_cond_t *c, pthread_mutex_t *m)  The wait() call takes a mutex as a parameter and assumes the lock is held when wait() is called  puts caller to sleep + releases the lock (atomically)  When the thread wakes up, it must re-acquire the lock before returning. 6 Pthread_cond_t c= PTHREAD_COND_INITIALIZER;
  • 7.
    Join Implementation: Attempt1 void thread_exit() { Cond_signal(&c); } void thread_join() { Cond_wait(&c); } Parent: Child: Will it Work? No. If child runs before parent calls joins, it will sleep forever
  • 8.
    Join Implementation: Attempt2 void thread_join() { Mutex_lock(&m); // w if (done == 0) // x Cond_wait(&c, &m); // y Mutex_unlock(&m); // z } Parent: x (y wont be executed) Child: a b Fixes previous broken ordering: void thread_exit() { done = 1; // a Cond_signal(&c); // b } void thread_join() { if (done == 0) // x Cond_wait(&c); // y } Parent: Child:
  • 9.
    Rule of Thumb1  Need to Keep state in addition to CV’s!  CV’s are used to signal threads when state changes  If state is already as needed, thread shouldn’t wait for a signal!
  • 10.
    Parent: x y Child:a b … sleep forever … Can you construct ordering that does not work? Join Implementation: Attempt 2 void thread_join() { Mutex_lock(&m); // w if (done == 0) // x Cond_wait(&c, &m); // y Mutex_unlock(&m); // z } void thread_exit() { done = 1; // a Cond_signal(&c); // b } void thread_join() { if (done == 0) // x Cond_wait(&c ); // y } Parent: Child:
  • 11.
    Join Implementation: Attempt3 using locks void thread_join() { Mutex_lock(&m); // w if (done == 0) // x Cond_wait(&c, &m); // y Mutex_unlock(&m); // z } void thread_exit() { Mutex_lock(&m); // a done =1; // b Cond_signal(&c); // c Mutex_unlock(&m);// d } void thread_join() { Mutex_lock(&m); // w if (done == 0) // x Cond_wait(&c); // y Mutex_unlock(&m); // z } Parent: Child: • Fixes previous problem but the parent should relea se lock before calling wait otherwise child can not change the state and signal. • Releasing the lock before wait() will bring the earlie r issue’s en-countered with Queue lock
  • 12.
    Join Implementation: Correctone! void thread_join() { Mutex_lock(&m); // w if (done == 0) // x Cond_wait(&c, &m); // y Mutex_unlock(&m); // z } void thread_exit() { Mutex_lock(&m); // a done =1; // b Cond_signal(&c); // c Mutex_unlock(&m);// d } void thread_join() { Mutex_lock(&m); // w if (done == 0) // x Cond_wait(&c, &m); // y Mutex_unlock(&m); // z } Parent: Child: • Therefore, the wait() operation on condition variable also takes the lock as another argument and it releas es the lock before putting thread to sleep, so lock i s available for child thread to signal. • when awoken, it reacquires the lock before returning • Rule of thumb 2: Always do wait/signal with lock he
  • 13.
    Parent waiting forChild: Use a condition variable 13 1 int done = 0; 2 pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER; 3 pthread_cond_t c = PTHREAD_COND_INITIALIZER; 4 5 void thr_exit() { 6 Pthread_mutex_lock(&m); 7 done = 1; 8 Pthread_cond_signal(&c); 9 Pthread_mutex_unlock(&m); 10 } 11 12 void *child(void *arg) { 13 printf("childn"); 14 thr_exit(); 15 return NULL; 16 } 17 18 void thr_join() { 19 Pthread_mutex_lock(&m); 20 while (done == 0) 21 Pthread_cond_wait(&c, &m); 22 Pthread_mutex_unlock(&m); 23 }
  • 14.
    Parent waiting forChild: Use a condition variable 14 (cont.) 25 int main(int argc, char *argv[]) { 26 printf("parent: beginn"); 27 pthread_t p; 28 Pthread_create(&p, NULL, child, NULL); 29 thr_join(); 30 printf("parent: endn"); 31 return 0; 32 }
  • 15.
    The Producer /Consumer (Bounded Buffer) Problem  Producer  Produce data items  Wish to place data items in a buffer  Consumer  Grab data items out of the buffer consume them in some way  Example: Multi-threaded web server  Client (producer) thread puts HTTP requests in to a work queue  Server (Consumer) threads take requests out of this queue for processing  When you pipe the output of one program into another.  Example: grep foo file.txt | wc –l  The grep process is the producer and wc process is the consumer.  Between them is an in-kernel bounded buffer 15
  • 16.
    Solution  General strategyuse condition variables to:  make producers wait when buffers are full  make consumers wait when there is nothing to consume  We will start with easy case:  1 producer thread  1 consumer thread  1 shared buffer slot to fill/consume (i.e max_size = 1) 16
  • 17.
    Producer/Consumer Threads  Producerputs an integer into the shared buffer loops number of times.  Consumer gets the data out of that shared buffer.  Try yourself writing the synchronized code…  Let Numfull = number of buffer slots currently filled 17 Youjip Won 1 void *producer(void *arg) { 2 int i; 3 int loops = (int) arg; 4 for (i = 0; i < loops; i++) { 5 do_fill(i); 6 } 7 } 8 void *consumer(void *arg) { 10 int i; 11 while (1) { 12 int tmp = do_get(); 13 printf("%dn", tmp); 14 } 15 }
  • 18.
    void *consumer(void *arg){ while(1) { Mutex_lock(&m); if(numfull == 0) Cond_wait(&cond, &m ); int tmp = do_get(); Cond_signal(&cond); Mutex_unlock(&m); printf(“%dn”, tmp); } } [RUNNABLE] [RUNNING] numfull=0 void *producer(void *arg) { for (int i=0; i<loops; i++) { Mutex_lock(&m); if(numfull == max) Cond_wait(&cond, &m ); do_fill(i); Cond_signal(&cond); Mutex_unlock(&m); } }
  • 19.
    [RUNNABLE] [RUNNING] numfull=0 void *consumer(void*arg) { while(1) { Mutex_lock(&m); if(numfull == 0) Cond_wait(&cond, &m ); int tmp = do_get(); Cond_signal(&cond); Mutex_unlock(&m); printf(“%dn”, tmp); } } void *producer(void *arg) { for (int i=0; i<loops; i++) { Mutex_lock(&m); if(numfull == max) Cond_wait(&cond, &m ); do_fill(i); Cond_signal(&cond); Mutex_unlock(&m); } }
  • 20.
    [RUNNABLE] [RUNNING] numfull=0 void *consumer(void*arg) { while(1) { Mutex_lock(&m); if(numfull == 0) Cond_wait(&cond, &m ); int tmp = do_get(); Cond_signal(&cond); Mutex_unlock(&m); printf(“%dn”, tmp); } } void *producer(void *arg) { for (int i=0; i<loops; i++) { Mutex_lock(&m); if(numfull == max) Cond_wait(&cond, &m ); do_fill(i); Cond_signal(&cond); Mutex_unlock(&m); } }
  • 21.
    [RUNNABLE] [RUNNING] numfull=0 void *consumer(void*arg) { while(1) { Mutex_lock(&m); if(numfull == 0) Cond_wait(&cond, &m ); int tmp = do_get(); Cond_signal(&cond); Mutex_unlock(&m); printf(“%dn”, tmp); } } void *producer(void *arg) { for (int i=0; i<loops; i++) { Mutex_lock(&m); if(numfull == max) Cond_wait(&cond, &m ); do_fill(i); Cond_signal(&cond); Mutex_unlock(&m); } }
  • 22.
    void *consumer(void *arg){ while(1) { Mutex_lock(&m); if(numfull == 0) Cond_wait(&cond, &m ); int tmp = do_get(); Cond_signal(&cond); Mutex_unlock(&m); printf(“%dn”, tmp); } } [RUNNABLE] [SLEEPING] numfull=0 void *producer(void *arg) { for (int i=0; i<loops; i++) { Mutex_lock(&m); if(numfull == max) Cond_wait(&cond, &m ); do_fill(i); Cond_signal(&cond); Mutex_unlock(&m); } }
  • 23.
    void *consumer(void *arg){ while(1) { Mutex_lock(&m); if(numfull == 0) Cond_wait(&cond, &m ); int tmp = do_get(); Cond_signal(&cond); Mutex_unlock(&m); printf(“%dn”, tmp); } } [RUNNING] [SLEEPING] numfull=0 void *producer(void *arg) { for (int i=0; i<loops; i++) { Mutex_lock(&m); if(numfull == max) Cond_wait(&cond, &m ); do_fill(i); Cond_signal(&cond); Mutex_unlock(&m); } }
  • 24.
    void *consumer(void *arg){ while(1) { Mutex_lock(&m); if(numfull == 0) Cond_wait(&cond, &m ); int tmp = do_get(); Cond_signal(&cond); Mutex_unlock(&m); printf(“%dn”, tmp); } } [RUNNING] [SLEEPING] numfull=0 void *producer(void *arg) { for (int i=0; i<loops; i++) { Mutex_lock(&m); if(numfull == max) Cond_wait(&cond, &m ); do_fill(i); Cond_signal(&cond); Mutex_unlock(&m); } }
  • 25.
    void *consumer(void *arg){ while(1) { Mutex_lock(&m); if(numfull == 0) Cond_wait(&cond, &m ); int tmp = do_get(); Cond_signal(&cond); Mutex_unlock(&m); printf(“%dn”, tmp); } } [RUNNING] [SLEEPING] numfull=0 void *producer(void *arg) { for (int i=0; i<loops; i++) { Mutex_lock(&m); if(numfull == max) Cond_wait(&cond, &m ); do_fill(i); Cond_signal(&cond); Mutex_unlock(&m); } }
  • 26.
    void *consumer(void *arg){ while(1) { Mutex_lock(&m); if(numfull == 0) Cond_wait(&cond, &m ); int tmp = do_get(); Cond_signal(&cond); Mutex_unlock(&m); printf(“%dn”, tmp); } } [RUNNING] [SLEEPING] numfull=0 void *producer(void *arg) { for (int i=0; i<loops; i++) { Mutex_lock(&m); if(numfull == max) Cond_wait(&cond, &m ); do_fill(i); Cond_signal(&cond); Mutex_unlock(&m); } }
  • 27.
    void *consumer(void *arg){ while(1) { Mutex_lock(&m); if(numfull == 0) Cond_wait(&cond, &m ); int tmp = do_get(); Cond_signal(&cond); Mutex_unlock(&m); printf(“%dn”, tmp); } } [RUNNING] [SLEEPING] numfull=1 void *producer(void *arg) { for (int i=0; i<loops; i++) { Mutex_lock(&m); if(numfull == max) Cond_wait(&cond, &m ); do_fill(i); Cond_signal(&cond); Mutex_unlock(&m); } }
  • 28.
    void *consumer(void *arg){ while(1) { Mutex_lock(&m); if(numfull == 0) Cond_wait(&cond, &m ); int tmp = do_get(); Cond_signal(&cond); Mutex_unlock(&m); printf(“%dn”, tmp); } } [RUNNING] [RUNNABLE] numfull=1 void *producer(void *arg) { for (int i=0; i<loops; i++) { Mutex_lock(&m); if(numfull == max) Cond_wait(&cond, &m ); do_fill(i); Cond_signal(&cond); Mutex_unlock(&m); } }
  • 29.
    void *consumer(void *arg){ while(1) { Mutex_lock(&m); if(numfull == 0) Cond_wait(&cond, &m ); int tmp = do_get(); Cond_signal(&cond); Mutex_unlock(&m); printf(“%dn”, tmp); } } [RUNNING] [RUNNABLE] numfull=1 void *producer(void *arg) { for (int i=0; i<loops; i++) { Mutex_lock(&m); if(numfull == max) Cond_wait(&cond, &m ); do_fill(i); Cond_signal(&cond); Mutex_unlock(&m); } }
  • 30.
    void *consumer(void *arg){ while(1) { Mutex_lock(&m); if(numfull == 0) Cond_wait(&cond, &m ); int tmp = do_get(); Cond_signal(&cond); Mutex_unlock(&m); printf(“%dn”, tmp); } } [RUNNING] [RUNNABLE] numfull=1 void *producer(void *arg) { for (int i=0; i<loops; i++) { Mutex_lock(&m); if(numfull == max) Cond_wait(&cond, &m ); do_fill(i); Cond_signal(&cond); Mutex_unlock(&m); } }
  • 31.
    void *consumer(void *arg){ while(1) { Mutex_lock(&m); if(numfull == 0) Cond_wait(&cond, &m ); int tmp = do_get(); Cond_signal(&cond); Mutex_unlock(&m); printf(“%dn”, tmp); } } [RUNNING] [RUNNABLE] numfull=1 void *producer(void *arg) { for (int i=0; i<loops; i++) { Mutex_lock(&m); if(numfull == max) Cond_wait(&cond, &m ); do_fill(i); Cond_signal(&cond); Mutex_unlock(&m); } }
  • 32.
    void *consumer(void *arg){ while(1) { Mutex_lock(&m); if(numfull == 0) Cond_wait(&cond, &m ); int tmp = do_get(); Cond_signal(&cond); Mutex_unlock(&m); printf(“%dn”, tmp); } } [RUNNING] [RUNNABLE] numfull=1 void *producer(void *arg) { for (int i=0; i<loops; i++) { Mutex_lock(&m); if(numfull == max) Cond_wait(&cond, &m ); do_fill(i); Cond_signal(&cond); Mutex_unlock(&m); } }
  • 33.
    void *producer(void *arg){ for (int i=0; i<loops; i++) { Mutex_lock(&m); if(numfull == max) Cond_wait(&cond, &m ); do_fill(i); Cond_signal(&cond); Mutex_unlock(&m); } } void *consumer(void *arg) { while(1) { Mutex_lock(&m); if(numfull == 0) Cond_wait(&cond, &m ); int tmp = do_get(); Cond_signal(&cond); Mutex_unlock(&m); printf(“%dn”, tmp); } } [SLEEPING] [RUNNABLE] numfull=1
  • 34.
    void *consumer(void *arg){ while(1) { Mutex_lock(&m); if(numfull == 0) Cond_wait(&cond, &m ); int tmp = do_get(); Cond_signal(&cond); Mutex_unlock(&m); printf(“%dn”, tmp); } } void *producer(void *arg) { for (int i=0; i<loops; i++) { Mutex_lock(&m); if(numfull == max) Cond_wait(&cond, &m ); do_fill(i); Cond_signal(&cond); Mutex_unlock(&m); } } [SLEEPING] [RUNNING] numfull=1
  • 35.
    void *producer(void *arg){ for (int i=0; i<loops; i++) { Mutex_lock(&m); if(numfull == max) Cond_wait(&cond, &m ); do_fill(i); Cond_signal(&cond); Mutex_unlock(&m); } } [SLEEPING] numfull=1 [RUNNING] void *consumer(void *arg) { while(1) { Mutex_lock(&m); if(numfull == 0) Cond_wait(&cond, &m ); int tmp = do_get(); Cond_signal(&cond); Mutex_unlock(&m); printf(“%dn”, tmp); } }
  • 36.
    void *producer(void *arg){ for (int i=0; i<loops; i++) { Mutex_lock(&m); if(numfull == max) Cond_wait(&cond, &m ); do_fill(i); Cond_signal(&cond); Mutex_unlock(&m); } } [SLEEPING] numfull=0 [RUNNING] void *consumer(void *arg) { while(1) { Mutex_lock(&m); if(numfull == 0) Cond_wait(&cond, &m ); int tmp = do_get(); Cond_signal(&cond); Mutex_unlock(&m); printf(“%dn”, tmp); } }
  • 37.
    void *producer(void *arg){ for (int i=0; i<loops; i++) { Mutex_lock(&m); if(numfull == max) Cond_wait(&cond, &m ); do_fill(i); Cond_signal(&cond); Mutex_unlock(&m); } } [RUNNABLE] numfull=0 [RUNNING] void *consumer(void *arg) { while(1) { Mutex_lock(&m); if(numfull == 0) Cond_wait(&cond, &m ); int tmp = do_get(); Cond_signal(&cond); Mutex_unlock(&m); printf(“%dn”, tmp); } }
  • 38.
    void *producer(void *arg){ for (int i=0; i<loops; i++) { Mutex_lock(&m); if(numfull == max) Cond_wait(&cond, &m ); do_fill(i); Cond_signal(&cond); Mutex_unlock(&m); } } numfull=0 [RUNNING] [RUNNABLE] void *consumer(void *arg) { while(1) { Mutex_lock(&m); if(numfull == 0) Cond_wait(&cond, &m ); int tmp = do_get(); Cond_signal(&cond); Mutex_unlock(&m); printf(“%dn”, tmp); } }
  • 39.
    void *producer(void *arg){ for (int i=0; i<loops; i++) { Mutex_lock(&m); if(numfull == max) Cond_wait(&cond, &m ); do_fill(i); Cond_signal(&cond); Mutex_unlock(&m); } } numfull=0 [RUNNING] [RUNNABLE] void *consumer(void *arg) { while(1) { Mutex_lock(&m); if(numfull == 0) Cond_wait(&cond, &m ); int tmp = do_get(); Cond_signal(&cond); Mutex_unlock(&m); printf(“%dn”, tmp); } }
  • 40.
    void *producer(void *arg){ for (int i=0; i<loops; i++) { Mutex_lock(&m); if(numfull == max) Cond_wait(&cond, &m ); do_fill(i); Cond_signal(&cond); Mutex_unlock(&m); } } numfull=0 [RUNNING] [RUNNABLE] void *consumer(void *arg) { while(1) { Mutex_lock(&m); if(numfull == 0) Cond_wait(&cond, &m ); int tmp = do_get(); Cond_signal(&cond); Mutex_unlock(&m); printf(“%dn”, tmp); } }
  • 41.
    void *producer(void *arg){ for (int i=0; i<loops; i++) { Mutex_lock(&m); if(numfull == max) Cond_wait(&cond, &m ); do_fill(i); Cond_signal(&cond); Mutex_unlock(&m); } } numfull=0 [RUNNING] [RUNNABLE] void *consumer(void *arg) { while(1) { Mutex_lock(&m); if(numfull == 0) Cond_wait(&cond, &m ); int tmp = do_get(); Cond_signal(&cond); Mutex_unlock(&m); printf(“%dn”, tmp); } }
  • 42.
    void *producer(void *arg){ for (int i=0; i<loops; i++) { Mutex_lock(&m); if(numfull == max) Cond_wait(&cond, &m ); do_fill(i); Cond_signal(&cond); Mutex_unlock(&m); } } numfull=0 [RUNNING] [RUNNABLE] void *consumer(void *arg) { while(1) { Mutex_lock(&m); if(numfull == 0) Cond_wait(&cond, &m ); int tmp = do_get(); Cond_signal(&cond); Mutex_unlock(&m); printf(“%dn”, tmp); } }
  • 43.
    void *producer(void *arg){ for (int i=0; i<loops; i++) { Mutex_lock(&m); if(numfull == max) Cond_wait(&cond, &m ); do_fill(i); Cond_signal(&cond); Mutex_unlock(&m); } } void *consumer(void *arg) { while(1) { Mutex_lock(&m); if(numfull == 0) Cond_wait(&cond, &m ); int tmp = do_get(); Cond_signal(&cond); Mutex_unlock(&m); printf(“%dn”, tmp); } } numfull=0 [SLEEPING] [RUNNABLE]
  • 44.
    void *consumer(void *arg){ while(1) { Mutex_lock(&m); if(numfull == 0) Cond_wait(&cond, &m ); int tmp = do_get(); Cond_signal(&cond); Mutex_unlock(&m); printf(“%dn”, tmp); } } void *producer(void *arg) { for (int i=0; i<loops; i++) { Mutex_lock(&m); if(numfull == max) Cond_wait(&cond, &m ); do_fill(i); Cond_signal(&cond); Mutex_unlock(&m); } } numfull=0 [SLEEPING] [RUNNING]
  • 45.
    void *consumer(void *arg){ while(1) { Mutex_lock(&m); if(numfull == 0) Cond_wait(&cond, &m ); int tmp = do_get(); Cond_signal(&cond); Mutex_unlock(&m); printf(“%dn”, tmp); } } numfull=0 [SLEEPING] [RUNNING] void *producer(void *arg) { for (int i=0; i<loops; i++) { Mutex_lock(&m); if(numfull == max) Cond_wait(&cond, &m ); do_fill(i); Cond_signal(&cond); Mutex_unlock(&m); } }
  • 46.
    void *consumer(void *arg){ while(1) { Mutex_lock(&m); if(numfull == 0) Cond_wait(&cond, &m ); int tmp = do_get(); Cond_signal(&cond); Mutex_unlock(&m); printf(“%dn”, tmp); } } numfull=1 [SLEEPING] [RUNNING] void *producer(void *arg) { for (int i=0; i<loops; i++) { Mutex_lock(&m); if(numfull == max) Cond_wait(&cond, &m ); do_fill(i); Cond_signal(&cond); Mutex_unlock(&m); } }
  • 47.
    void *consumer(void *arg){ while(1) { Mutex_lock(&m); if(numfull == 0) Cond_wait(&cond, &m ); int tmp = do_get(); Cond_signal(&cond); Mutex_unlock(&m); printf(“%dn”, tmp); } } numfull=1 [RUNNABLE] [RUNNING] void *producer(void *arg) { for (int i=0; i<loops; i++) { Mutex_lock(&m); if(numfull == max) Cond_wait(&cond, &m ); do_fill(i); Cond_signal(&cond); Mutex_unlock(&m); } } With just a single producer and consumer the code works fine!
  • 48.
    What about 2consumers? Can you find a problematic timeline with 2 consumers (still 1 producer)? void *consumer(void *arg) { while(1) { Mutex_lock(&m); //c1 if(numfull == 0) //c2 Cond_wait(&cond, &m); / /c3 int tmp = do_get(); //c4 Cond_signal(&cond); //c5 Mutex_unlock(&m); //c6 printf(“%dn”, tmp); //c7 } } void *producer(void *arg) { for (int i=0; i<loops; i++) { Mutex_lock(&m);//p1 if(numfull == max)//p2 Cond_wait(&cond, &m); // p3 do_fill(i); //p4 Cond_signal(&cond); //p5 Mutex_unlock(&m); //p6 } }
  • 49.
    Rule of Thumb3  Whenever a lock is acquired, recheck assumptions about state! i.e use While loops (not if) with CV’s.  Possible for another thread to grab lock in between signal and wakeup from wait  Sometimes you don’t need to recheck the condition but it is always safe to do so;  just do it and be happy!
  • 50.
    The code stillhas a bug! Can you find another problematic timeline with 2 consumers (still 1 producer)? void *consumer(void *arg) { while(1) { Mutex_lock(&m); //c1 while(numfull == 0) //c2 Cond_wait(&cond, &m); / /c3 int tmp = do_get(); //c4 Cond_signal(&cond); //c5 Mutex_unlock(&m); //c6 printf(“%dn”, tmp); //c7 } } void *producer(void *arg) { for (int i=0; i<loops; i++) { Mutex_lock(&m);//p1 while(numfull == max)//p2 Cond_wait(&cond, &m); // p3 do_fill(i); //p4 Cond_signal(&cond); //p5 Mutex_unlock(&m); //p6 } }
  • 51.
    How to wakethe right thread?  One solution: wake all the threads! broadcast(cond_t *cv) - wakes all waiting threads (if >= 1 thread is waiting) - if there are no waiting thread, just return, doing nothing  Example use: multi-threaded memory allocation lib void *allocate(int size) { mutex_lock(&m); while (bytesLeft < size) cond_wait(&c); … } void free(void *ptr, int size) { … cond_broadcast(&c) … } Any disadvantage? Should it be used for producer/consumer problem?
  • 52.
    Producer/Consumer: Two CVs Isthis correct? Can you find a bad schedule? Correct! - no concurrent access to shared state - every time lock is acquired, assumptions are reevaluated - a consumer will get to run after every do_fill() - a producer will get to run after every do_get() void *consumer(void *arg) { while(1) { Mutex_lock(&m); //c1 while(numfull == 0) //c2 Cond_wait(&fill, &m); //c 3 int tmp = do_get(); //c4 Cond_signal(&empty); //c5 Mutex_unlock(&m); //c6 printf(“%dn”, tmp); //c7 } } void *producer(void *arg) { for (int i=0; i<loops; i++) { Mutex_lock(&m);//p1 while(numfull == max)//p2 Cond_wait(&empty, &m); // p3 do_fill(i); //p4 Cond_signal(&fill); //p5 Mutex_unlock(&m); //p6 } }
  • 53.
    Summary: rules ofthumb for CVs  Keep state in addition to CV’s  Always do wait/signal with lock held  Whenever thread wakes from waiting, recheck state
  • 54.
    Circular buffer withN elements  Add more buffer slots  More concurrency and efficiency.  enables multiple values to be produced or consumed.  Reduce context switches. 54 1 int buffer[MAX]; 2 int in = 0; 3 int out = 0; 4 int count = 0; 5 6 void do_fill(int value) { 7 buffer[in] = value; 8 in = (in + 1) % MAX; 9 count++; 10 } 11 12 int do_get() { 13 int tmp = buffer[out]; 14 out = (out + 1) % MAX; 15 count--; 16 return tmp; 17 } The do_fill( ) and do_get( ) Routines
  • 55.
    The Final Producer/ConsumerSolution (Cont.) 55 1 cond_t empty, fill; 2 mutex_t mutex; 4 void *producer(void *arg) { 5 int i; 6 for (i = 0; i < loops; i++) { 7 Pthread_mutex_lock(&mutex); // p1 8 while (count == MAX) // p2 9 Pthread_cond_wait(&empty, &mutex); // p3 10 do_fill(i); // p4 11 Pthread_cond_signal(&fill); // p5 12 Pthread_mutex_unlock(&mutex); // p6 13 } 14 } 15 16 void *consumer(void *arg) { 17 int i; 18 for (i = 0; i < loops; i++) { 19 Pthread_mutex_lock(&mutex); // c1 20 while (count == 0) // c2 21 Pthread_cond_wait(&fill, &mutex); // c3 22 int tmp = do_get(); // c4 23 Pthread_cond_signal(&empty); // c5 24 Pthread_mutex_unlock(&mutex); // c6 25 printf("%dn", tmp); 26 } 27 }