Pthread (continue) • General pthread program structure – Encapsulate parallel parts (can be almost the whole program) in functions. – Use function arguments to parameterize what a particular thread does. • Usually needs to know myid and nprocs. • Add synchronization when necessary – Call pthread_create() with the function and arguments, save thread identifier returned. – Call pthread_join() with that thread identifier Thread synchronizations • When more than one thread works on the same task, the threads often need to coordinate their activities to ensure correct behavior. – Coordination that results in synchronization or communication is the inherent price to pay when going multithreading. • Coordination often means waiting! Motivating Example: Too Much Milk • Two robots are programmed to maintain the milk inventory at a store… • They are not aware of each other’s presence… Robot: Dumb Robot: Dumber Motivating Example: Too Much Milk Dumb 4:00 Look into fridge: Out of milk Dumber Motivating Example: Too Much Milk Dumb 4:00 Look into fridge: Out of milk 4:05 Head for the warehouse Dumber Motivating Example: Too Much Milk Dumb 4:05 Head for the warehouse Dumber 4:10 Look into fridge: Out of milk Motivating Example: Too Much Milk Dumb Dumber 4:10 Look into fridge: Out of milk 4:15 Head for the warehouse Motivating Example: Too Much Milk Dumb 4:20 Dumber 4:15 Head for the warehouse Arrive with milk Motivating Example: Too Much Milk Dumb 4:20 Dumber 4:15 Head for the warehouse Arrive with milk Motivating Example: Too Much Milk Dumb 4:20 Arrive with milk 4:25 Go party Dumber Motivating Example: Too Much Milk Dumb 4:20 Arrive with milk 4:25 Go party Dumber 4:30 Arrive with milk: “Uh oh…” Common coordination constructs • Critical section: a piece of code that only one thread can execute at a time – Only one thread can go get milk at one time. • Mutual exclusion: ensure one thread can do something without the interference of other threads – When I print, nobody else should be printing. Common coordination constructs • Synchronization: use atomic operations to ensure cooperation among threads – Event synchronization T1 … X = 400 … T2 … Y = X+1 ... T1 … X = 400 X ready … … T2 … …. wait for X Y=X+1 ... Pthreads synchronization support • Mutex locks – Critical session and mutual exclusion • Condition variables – Event synchronization • Semaphores – Both (in UNIX, not pthread) Mutex locks: lock/unlock • pthread_mutex_lock(pthread_mutex_t *mutex); – Tries to acquire the lock specified by mutex – If mutex is already locked, then the calling thread blocks until mutex is unlocked. • At one time, only one thread can get the lock Mutex locks: lock/unlock • pthread_mutex_unlock(pthread_mutex_t *mutex); – If the calling thread has mutex currently locked, this will unlock the mutex. – If other threads are blocked waiting on this mutex, one will unblock and acquire mutex. – Which one is determined by the scheduler. Lock and critical section • A lock prevents a thread from doing something – A thread should lock before entering a critical section – A thread should unlock when leaving the critical section – A thread should wait if the critical section is locked • Synchronization often involves waiting Mutex lock– for mutual exclusion int counter = 0; void *thread_func(void *arg) { int val; /* unprotected code – why? */ val = counter; counter = val + 1; return NULL; } Mutex example int counter = 0; ptread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; void *thread_func(void *arg) { int val; /* protected by mutex */ Pthread_mutex_lock( &mutex ); val = counter; counter = val + 1; Pthread_mutex_unlock( &mutex ); return NULL; } Condition variables – and event synchronization • Think of Producer – consumer problem • Producers and consumers run in separate threads. • Producer produces data and consumer consumes data. • Producer has to inform the consumer when data is available • Consumer has to inform producer when buffer space is available Condition variables: wait • Pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex) – Blocks the calling thread, waiting on cond. – Unlock the mutex – Re-acquires the mutex when unblocked. Condition variables: signal • Pthread_cond_signal(pthread_cond_t *cond) – Unblocks one thread waiting on cond. – The scheduler determines which thread to unblock. – If no thread waiting, then signal is a no-op Producer consumer program without condition variables /* Globals */ int data_avail = 0; pthread_mutex_t data_mutex = PTHREAD_MUTEX_INITIALIZER; void *producer(void *) { Pthread_mutex_lock(&data_mutex); Produce data Insert data into queue; data_avail=1; Pthread_mutex_unlock(&data_mutex); } void *consumer(void *) { while( !data_avail ); /* do nothing – keep looping!!*/ Pthread_mutex_lock(&data_mutex); Extract data from queue; if (queue is empty) data_avail = 0; Pthread_mutex_unlock(&data_mutex); consume_data(); } Producer consumer with condition variables int data_avail = 0; pthread_mutex_t data_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cont_t data_cond = PTHREAD_COND_INITIALIZER; void *producer(void *) { Pthread_mutex_lock(&data_mutex); Produce data Insert data into queue; data_avail = 1; Pthread_cond_signal(&data_cond); Pthread_mutex_unlock(&data_mutex); } void *consumer(void *) { Pthread_mutex_lock(&data_mutex); while( !data_avail ) { /* sleep on condition variable*/ Pthread_cond_wait(&data_cond, &data_mutex); } /* woken up */ Extract data from queue; if (queue is empty) data_avail = 0; Pthread_mutex_unlock(&data_mutex); consume_data(); } A note on condition variables • A signal is forgotten if there is no corresponding wait that has already occurred. • If you want the signal to be remembered, use semaphores. Semaphores • Counters for resources shared between threads. Sem_wait(sem_t *sem) – Blocks until the semaphore vale is non-zero – Decrements the semaphore value on return. Sem_post(sem_t *sem) – Unblocks the semaphore and unblocks one waiting thread – Increments the semaphore value otherwise Pipelined task parallelism with semaphore P1: for (I=0; I<num_pics, read(in_pic); I++) { int_pic_1[I] = trans1(in_pic); sem_post(event_1_2[I]); } P2: for (I=0; I<num_pics; I++) { sem_wait(event_1_2[I]); int_pic_2[I] = trans2(int_pic_1[I]); sem_post(event_2_3[I]); } Challenges with thread programming • Race condition: occurs when multiple threads and write to the same memory location. – Solution with a coordination mechanism: lock, conditional variable, semaphore, etc • Coordination results in waiting among threads – Deadlocks: occur when threads are waiting for resources with circular dependencies Deadlock example T1: … Lock(printer) … Lock (keyboard) … Unlock(keyboard) … Unlock(printer) T2: … lock(keyboard) … lock(printer) … unlock(printer) … unlock(keyboard) Deadlock and third party software • In sequence programs, using third party software is trivial. – Call “system(“/usr/bin/ls”); – No knowledge about /usr/bin/ls is needed. • Could deadlock happen in thread programming by calling a third party program? – This is a big problem facing multi-thread programming. Summary • What is thread coordination (synchronization)? Why? • What are the common thread coordinations? • Pthread’s support for thread coordination. – Mutex lock – Condition variable • Deadlock • Thread programming issues