CS 420 - Lecture 8

Pthreads programming

"pthreads" = POSIX threads

System thread API supported by Linux, Solaris, MacOS X, other Unix

pthreads-win32: layers the pthreads API on top of the Win32 thread API

Two primary synchronization primitives:

mutex: ensure that only one thread at a time can access shared data

guarantee the atomicity of a sequence of accesses to shared data

condition variable: allow threads to wait for some condition to be true

basically, a queue of waiting threads

Mutexes

"Mutex" means "mutual exclusion"

Declaring a mutex - simply a variable whose type is pthread_mutex_t

Typically, each shared data structure that needs to be protected is associated with one mutex

The mutex can be (and usually is) placed inside the data structure to be protected

General rule: accesses to shared data should only be made while the mutex is locked

Functions:

pthread_mutex_init: initialize a mutex

must be done before mutex is used for the first time

pthread_mutex_destroy: destory a mutex

should be done after all threads are done using the mutex

pthread_mutex_lock: lock/acquire the mutex

called at beginning of sequence of operations requiring exclusive access to shared data

wait until no other thread is using the mutex, then lock it for exclusive access

pthread_mutex_unlock: unlock/release previously acquired mutex

called at end of sequence of operations requiring exclusive access to shared data

allow other threads to contend for access to the shared data

Mutex Example: atomic counter

Condition variables

Declaring a condition variable - variable whose type is pthread_cond_t

Allow threads to sleep waiting for a condition

something that must be true in order for the continue doing useful work

a condition is a predicate on the state of shared data

Threads that enable a condition (making a condition true) must "wake up" threads waiting on the condition

The condition must be associated with a mutex

The mutex must be locked when a thread waits on or enables the condition

Functions:

pthread_cond_init: initialize a condition variable

must be done before any thread uses the condition variable for the first time

pthread_cond_wait: atomically unlocks a mutex (which must previously have been locked) and adds the calling thread to the condition variable's wait queue.  When the thread wakes up and the call returns, the thread will have locked the mutex again.

Two important criteria for correct use:

(1) The condition must be checked with the mutex held

Otherwise, another thread might change the shared data between the time the condition is checked and the time the thread waits.

(2) Must be used within a loop that checks the condition:

while ( condition is false ) {
pthread_cond_wait( &cond, &mutex );
}

In general, a condition variable may be used for multiple conditions: just because the thread was woken up doesn't necessarily mean that the condition it is waiting for is actually true.

pthread_cond_destroy: called after all threads are done using a condition variable

Example: producer/consumer shared bounded buffer

Common bugs

Deadlock:

If two threads acquire two different mutexes in different orders, they can enter a deadlock where neither can make progress.

  Thread 1      Thread 2
--------------------------
lock A
lock B
lock B
lock A

Each thread is put to sleep indefinitely because the other thread has already acquired the lock it is trying to acquire.

To prevent deadlocks, each thread must acquire locks in a consistent global order.

Data race:

Data races arise when two threads access shared data without synchronization, where at least one of the accesses is a write.  The counter lost update is an example of a data race.