140 likes | 178 Views
Condition Variables. Youngju Song(youngju.song@sf.snu.ac.kr) Jeongyoon Eo(jyeo@rubis.snu.ac.kr) School of Computer Science and Engineering Seoul National University. Contents. Condition Variables Motivation Condition Variables: Definition and Routines Producer/Consumer Problem
E N D
Condition Variables Youngju Song(youngju.song@sf.snu.ac.kr) Jeongyoon Eo(jyeo@rubis.snu.ac.kr) School of Computer Science and Engineering Seoul National University
Contents • Condition Variables • Motivation • Condition Variables: Definition and Routines • Producer/Consumer Problem • Covering Condition • Summary
Motivation • Concurrent programs depend on certain conditions • How to wait for the condition? • Spinning wastes CPU time • Sleep for a condition instead of spinning • If condition is met, continue execution • Else, sleep for the condition
Definition and Routines #include <pthread.h> pthread_cond_t c; // condition variable int pthread_cond_wait(pthread_cond_t *c, pthread_mutex_t *m); // wait() int pthread_cond_signal(pthread_cond_t *c); // signal() // returns 0 if OK, else error # • Condition Variable(CV) • A queue where threads wait on conditions • Routines associated with CV • Wait(CV, mutex) • Atomically, Release the mutex (assumes locked) Put calling thread to sleep • When wakes up, Re-acquire the mutex And then return • Signal(CV) • Wake one of the sleeping threads
CV Routines Example: join() int done = 0; pthread_cond_t c; Parent Child int main(int argc, char* argv[]){ printf(“parent: begin\n”); pthread_t p; pthread_create(&p, NULL, child, NULL); thr_join(); printf(“parent: end\n”); return 0; } void* child(void* arg){ printf(“child\n”); thr_exit(); return NULL; } Wakes Parent up void thr_exit(){ pthread_mutex_lock(&m); done = 1; pthread_cond_signal(&c); pthread_mutex_unlock(&m); } void thr_join(){ pthread_mutex_lock(&m); while(done == 0) pthread_cond_wait(&c, &m); pthread_mutex_unlock(&m); } Parent waits on a condition ‘child exit’ • Parent creates a child thread and waits on ‘child exit’ condition • Requirements • state variable ‘done’ • mutex
CV Routines Broken Example: join() X int done = 0; pthread_cond_t c; Parent Child int main(int argc, char* argv[]){ printf(“parent: begin\n”); pthread_t p; pthread_create(&p, NULL, child, NULL); thr_join(); printf(“parent: end\n”); return 0; } void* child(void* arg){ printf(“child\n”); thr_exit(); return NULL; } Wakes Up Nobody Waits Forever void thr_exit(){ pthread_mutex_lock(&m); pthread_cond_signal(&c); pthread_mutex_unlock(&m); } void thr_join(){ pthread_mutex_lock(&m); pthread_cond_wait(&c, &m); pthread_mutex_unlock(&m); } Parent waits on a condition ‘child exit’ • Are BOTH state variable and mutex ALWAYS needed? • YES! • Without the state variable, wait and signal operations get lost
CV Routines Broken Example: join() int done = 0; Parent Child int main(int argc, char* argv[]){ printf(“parent: begin\n”); pthread_t p; pthread_create(&p, NULL, child, NULL); thr_join(); printf(“parent: end\n”); return 0; } void* child(void* arg){ printf(“child\n”); thr_exit(); return NULL; } Wakes Up Nobody Waits Forever void thr_exit(){ done = 1; pthread_cond_signal(&c); } void thr_join(){ if(done == 0) pthread_cond_wait(&c, &m); } Interrupted Parent waits on a condition ‘child exit’ • Are BOTH state variable and mutex ALWAYS needed? • YES! • Without the mutex, race conditions arise; multiple threads share the state variable
Producer/Consumer Problem How to control access to the shared buffer? single entry shared buffer int buffer; int count = 0; Consumer Producer void* consumer(void* arg){ int i; while(1){ int tmp = get(); printf(“%d\n”, tmp); } } void* producer(void* arg){ int i; int loops = (int) arg; for(i=0; i < loops; i++) put(i); } void put(int value){ assert(count == 0); count = 1; buffer = value; } int get(){ assert(count == 1); count = 0; return buffer; } Only when buffer isn’t full, fill the buffer Only when buffer isn’t empty, empty the buffer • Producer • Generate data and put it in a buffer • Consumer • Take the data away from the buffer
Re-check the condition when awaken! Broken Solution: Using If pthread_cond_t c; mutex_t m = 0; int count = 0; Consumer: C1, C2 Producer: P Producer void* producer(void* arg){ int i; while(1){ pthread_mutex_lock(&m); if(count == 1) pthread_cond_wait(&c, &m); put(i); pthread_cond_signal(&c); pthread_mutex_unlock(&m); } } context switch p1 p2 p3 p4 p5 p6 wakes C1 up Consumer void* consumer(void* arg){ int i; while(1){ pthread_mutex_lock(&m); if(count == 0) pthread_cond_wait(&c, &m); int tmp = get(); pthread_cond_signal(&c); pthread_mutex_unlock(&m); print(“%d\n”, tmp); } } context switch c1 c2 c3 c4 c5 c6 consumes data context switch Buffer is empty!
Signaling should be directed! Broken Solution: Using While pthread_cond_t c; mutex_t m = 0; int count = 0; Consumer: C1, C2 Producer: P Producer void* producer(void* arg){ int i; while(1){ pthread_mutex_lock(&m); while(count == 1) pthread_cond_wait(&c, &m); put(i); pthread_cond_signal(&c); pthread_mutex_unlock(&m); } } context switch p1 p2 p3 p4 p5 p6 context switch wakes C1 up Consumer void* consumer(void* arg){ int i; while(1){ pthread_mutex_lock(&m); while(count == 0) pthread_cond_wait(&c, &m); int tmp = get(); pthread_cond_signal(&c); pthread_mutex_unlock(&m); print(“%d\n”, tmp); } } context switch c1 c2 c3 c4 c5 c6 wakes C2 up, NOT P context switch Sleeps forever!
Signaling should be directed! Broken Solution: Using While pthread_cond_t empty, fill; mutex_t m = 0; int count = 0; Consumer: C1, C2 Producer: P Producer void* producer(void* arg){ int i; while(1){ pthread_mutex_lock(&m); while(count == 1) pthread_cond_wait(&empty, &m); put(i); pthread_cond_signal(&fill); pthread_mutex_unlock(&m); } } context switch p1 p2 p3 p4 p5 p6 context switch wakes C1 up Consumer void* consumer(void* arg){ int i; while(1){ pthread_mutex_lock(&m); while(count == 0) pthread_cond_wait(&fill, &m); int tmp = get(); pthread_cond_signal(&empty); pthread_mutex_unlock(&m); print(“%d\n”, tmp); } } context switch c1 c2 c3 c4 c5 c6 wakes C2 up, NOT P context switch Sleeps forever!
Generalized Final Solution • Producer/consumer with a multi-slot buffer int buffer[MAX]; int fill_ptr = 0; int use_ptr = 0; pthread_cond_t empty, fill; mutex_t m = 0; int count = 0; Producer Consumer void* consumer(void* arg){ int i; while(1){ pthread_mutex_lock(&m); while(count == 0) pthread_cond_wait(&fill, &m); int tmp = get(); pthread_cond_signal(&empty); pthread_mutex_unlock(&m); print(“%d\n”, tmp); } } void* producer(void* arg){ int i; while(1){ pthread_mutex_lock(&m); while(count == MAX) pthread_cond_wait(&empty, &m); put(i); pthread_cond_signal(&fill); pthread_mutex_unlock(&m); } } int get(){ int tmp = buffer[use_ptr]; use_ptr = (use_ptr+1)%MAX; count--; return tmp; } void put(int value){ buffer[fill_ptr] = value; fill_ptr = (fill_ptr+1)%MAX; count++; }
Covering Condition Thread A alloc 100 Wake up Check cond Sleep should wait Thread C free 50 ? broadcast Wake up Check cond Get memory should receive memory Thread B alloc 10 • Signaling thread might not know which thread(s) to wake up • Example: a multi-threaded memory allocation library • Wait(): Sleep for enough memory to become free • Signal(): Wake up when amount of free memory is increased • Broadcast the signal and wake all waiting threads • Pros: Threads that should get the signal are guaranteed to receive it • Cons: Negative performance impact – too many threads can be awaken
Summary • Condition Variables • A queue where threads wait on conditions - wait() and signal() routines • Allows threads to sleep on condition: No need for spinning • Solves producer/consumer problem • Solves covering condition by broadcasting signals