--- title: Pthread Case Study tags: Multithread, pthread, linux --- ## PThread Introduction [PThread] (POSIX[^0] thread) a concrete multithreading system in UNIX systems. It defines the threading related API that operating systems need to support. This includes all the mechanisms required to create, manage, synchronise threads. Including threads themselves and other constructs such as Mutex and Conditional Variables. An abstract paper on multithreading by [Birrell] outlines a lot of what is covered here. ## PThread Creation To create a thread, 3 mechanisms is required. ```C // A type to descript a thread // ID, execution state, other info // Info used mostly by the pthread lib, not dev pthread_t aThread; ``` ```C // https://man7.org/linux/man-pages/man3/pthread_create.3.html // Birrell's Fork (proc, args) // start_routine = proc // it returns a status (int) and also create the thread // see below for pthread_attr_t int pthread_create(pthread_t *restrict thread, const pthread_attr_t *restrict attr, void *(*start_routine)(void *), void *restrict arg); ``` ```C //https://man7.org/linux/man-pages/man3/pthread_join.3.html // Birrell's join int pthread_join(pthread_t thread, void **retval); ``` ### PThread attributes The arg `const pthread_attr_t` `*restrict attr` can specify different feature of the new thread. - stack size - scheduling policy - scope (user / system) - joinable - priority - inheritance If you pass in NULL as the attribute, the default behaviour will be used. You can create this attribute with: ```C // create a default int pthread_attr_init(pthread_attr_t* attr) // Frees the attribute in memory int pthread_attr_destroy(pthread_attr_t* attr) // set / get value pthread_attr_{set/get}(pthread_attr_t* attr) ``` ### Detachable Thread PThread allows for detachable thread (which was not a concept in Birrell's paper). By **default**, new threads are joinable. A joinable thread **MUST** be reaped by a parent. i.e. if the parent exits, the children become *zombie* threads since they cannot be reaped (memeory resource free) properly. The concept of a detachable thread is such that the parent and children are much alike, only parent have some info of children. But parent can exit `pthread_exit()` and children can keep going and exit safely. ```C // To detach a thread int pthread_detach(pthread_t thread); // To create a detached thread int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate); ``` ## Mutex To ensure there are not unexpected race conditions, Birrell proposed Mutex which is supported by PThread. ```C // A Mutex type pthread_mutex_t aMutex; // A Mutex must be explicitly initialised int pthread_mutex_init(pthread_mutex_t *aMutex, const pthread_mutex_attr *attr); // A Mutex must be destroyed to free up resources int pthread_mutex_destroy(pthread_mutex_t *aMutex); // Explicit Lock // This will BLOCK the calling thread if mutex is not free int pthread_mutex_lock(thread_mutex_t *aMutex); // Explicit Unlock (different to Birrell) int pthread_mutex_unlock(thread_mutex_t *aMutex); // This will NOT BLOCK the calling thread if mutex is not free // It will return and give the calling thread a option to go do something else int pthread_mutex_trylock(thread_mutex_t *aMutex); ``` `pthread_mutex_attr` are attribute (if NULL, it is set to default) for the thread. Default behaviour is that the Mutex is private to the process (only shared between threads). However, you can make it shared between processes. ### Safety tips 1. Shared data should be done through a single Mutex 1. Mutex must be visible to all thread 1. Globally order the lock of the mutex (helps to avoid deadlock) 1. Always Unlock the correct mutex after use ## PThread Conditional Variable This is a synchronisation construct that allows blocked threads to be notified once certain condition occurs. The methods are: ```C // A condition variable type pthread_cont_t cond; // Must use init for allocating data structure int pthread_cont_init(pthread_cont_t *cond, pthread_condattr_t *attr) // Must destroy to deallocate memory int pthread_cont_destroy(pthread_cont_t *cond) // Wait // when some predicate is not met, the programmer can call a wait // A wait will add the calling thread to the pthread_cont_t Queue // it will also release the lock on the Mutex for other threads // // when the conditional is met, the thread will be woken up and do its thing, // perhaps again check the predicate and try to lock the mutex int pthread_cont_wait(pthread_cont_t *cont, pthread_mutex_t *mutex); // Signal // This lets thread signal and wake exactly 1 thread with the cond int pthread_cont_signal(pthread_cont_t *cond); // Broadcast // This lets threads broadcast and wake multiple threads with the cond int pthread_cont_broadcast(pthread_cont_t *cond); ``` There are other attributes, similar to Mutex, for `pthread_condattr_t`. i.e. do you want to share the condition variable between processes ### Tips 1. Do not forget to notify threads when a predicate changes 1. When in doubt, first use broadcast (perf loss, but at least no indefinitely waiting threads), then figure it out 1. Signal and Braodcast typically do not need the Mutex (predicate ususally is the mutex). If possible, release the Mutex before signal/broadcast. This will ensure theres not **spurious** wakeups, i.e. move from the cond_var queue to the mutex queue. ## Sidenotes *Order* of thread execution does not depend on when they get created. If you have global variable, to avoid race condition, use Mutex Might seem silly, but remember to link the pthread lib when compiling `gcc -o main main.c -lpthread` --- [PThread]: https://man7.org/linux/man-pages/man7/pthreads.7.html [Birrell]: https://s3.amazonaws.com/content.udacity-data.com/courses/ud923/references/ud923-birrell-paper.pdf [^0]: Portable Operating System Interface - defines the interface that OS needs to support. It is intended to increase interoperatbility amongst OSs.