POSIX pthreads
Note: It is assumed that you have a good understanding of the C programming language. If you do not or need to brush up, please review basic C (including pointers and dynamic memory allocation). Here are some resources.
Now that we have a good foundation of thread concepts, lets talk about a particular thread library, POSIX pthreads. The pthread library can be found on almost any modern OS.
A few preliminary steps you should take before beginning any pthread coding is to:
- add
in your .c or .h header file(s)#include <pthread.h>
- define the
macro somewhere in a common .h or .c file#define _REENTRANT
- In your
make sure gcc links againstMakefile
-lpthread
- Optional: add
to your-D_POSIX_PTHREAD_SEMANTICS
(gcc flag) for certain function calls likeMakefile
sigwait()
Pthread Basics
Now let's begin our journey into pthreads...
A thread is represented by the type
pthread_t
. Let's begin by examining most of the pthread creation and initializing functions:
int pthread_create(pthread_t *thread, pthread_attr_t *attr,
void *(*start_routine)(void *), void *arg);
int pthread_attr_init(pthread_attr_t *attr);
int pthread_mutex_init(pthread_mutex_t *mutex,
const pthread_mutexattr_t *mutexattr);
int pthread_cond_init(pthread_cond_t *cond,
pthread_condattr_t *cond_attr);
pthread_create()
example:
pthread_create(&pt_worker, &thread_attributes,
thread_function, (void *)thread_args);
The above will create a pthread
pt_worker
with thread attributes defined in
thread_attributes
(this argument can be
NULL
if you want default thread attributes). The thread code is contained in the function
thread_function
and is passed in a arguments stored in
thread_args
. The
thread_function
prototype would look like this:
void *thread_function(void *args);
Immediately after the pthread_create call completes, the
thread_function
will begin executing.
pthread_XXXX_init()
functions initialize thread attributes, mutexes, and condition variables.
mutexattr
and
cond_attr
can be NULL if you are using defaults. Mutexes and condition variables can be initialized to default values using the
INITIALIZER
macros as well. For example:
pthread_mutex_t count_lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t count_cond = PTHREAD_COND_INITIALIZER;
Pthread Mutexes
To perform locks and unlocks the following functions are available for you:
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
pthread_mutex_lock()
is a blocking call. If the thread cannot gain the lock, then the thread will block the thread from proceeding until it obtains the lock.
pthread_mutex_trylock()
will return immediately if the mutex cannot be locked. To unlock a mutex, simply call
pthread_mutex_unlock()
. An example on using Pthread mutexes:
pthread_mutex_lock(&count_lock);
count++;
pthread_mutex_unlock(&count_lock);
In the above example, we are incrementing the globally shared
count
variable. The code between the lock and unlock calls is the critical section. Always try to minimize this section!
Pthread Condition Variables
Here are the pthread condition variable function prototypes:
int pthread_cond_wait(pthread_cond_t *cond,
pthread_mutex_t *mutex);
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
pthread_cond_wait()
puts the current thread to sleep. It requires a mutex of the associated shared resource value it is waiting on.
pthread_cond_signal()
signals one thread out of the possibly many sleeping threads to wakeup.
pthread_cond_broadcast()
signals all threads waiting on the
cond
condition variable to wakeup. Here is an example on using pthread condition variables:
pthread_mutex_lock(&count_lock);
while (count < MAX_COUNT) {
pthread_cond_wait(&count_cond, &count_lock);
}
pthread_mutex_unlock(&count_lock);
We are locking the
count_lock
mutex so we can read the value of count without entering a potential race condition. Notice how the we use a
while
loop instead of an
if
statement. This is because of spurious wakeups problem previously mentioned. Just because a thread has been woken does not mean it was due to a
pthread_cond_signal()
or
pthread_cond_broadcast()
call. The
pthread_cond_wait()
call takes the count mutex and condition variable. Why does
pthread_cond_wait()
require the mutex as well as the conditon variable? It's because it needs to unlock the mutex when going to sleep or you would potentially enter into a deadlock!
pthread_cond_wait()
if awoken, automatically tries to reacquire the mutex, and will block if it cannot. Using the signal and broadcast functions is self-explanatory. It is recommended that you release any locks that other threads could be waiting on before you signal or broadcast.
Miscellaneous
Here are some suggestions and issues you should consider when creating using pthreads:
- One thing we have been disregarding in the above discussions is return values. You must check all return values! No exceptions!
- Sometimes it is desirable for a thread not to terminate (as in the case of the worker thread pool). This can be solved by placing the thread code in an infinite loop and using condition variables.
- Many of the pthread types can be "free'd" using the
calls.pthread_XXXX_destroy()
-
can be used to wait on other threads to finish (if the JOINABLE attribute is set). This is useful in creating barriers or other synchronization windows/points.pthread_join()
- To set thread attributes, use the
functions. scope, schedpolicy, and detachstate are only some of the useful attributes you can set on your threads.pthread_attr_setXXXX()
-
can be used to deliver signals to specific threads.pthread_kill()
-
returns a handle on the calling thread.pthread_self()
-
can be used to ensure that an initializing function within a thread is only run once.pthread_once()
- There are many, many more useful functions in the pthread library. Consult your man pages or the Nichols text (Appendix C).