Implementing POSIX Thread Barrier
In multi-threading, barriers are a synchronization method that can coordinate multiple threads working in parallel. A barrier allows threads to wait at some point. Once all cooperating threads reach the barrier, the threads wake up and continue its execution. Pthread barriers are defined in the optional part of POSIX standard. Therefore not all Unixes implement Pthread barriers. OS X is one of the OSes that does not have barriers.
The good news is that barriers are not very difficult to implement. Barriers can be implemented using mutexes and condition variables. The functions we are going to implement are pthread_barrier_init()
and pthread_barrier_wait()
. First let’s define the structure that encapsulates the states of the barrier.
typedef struct {
pthread_mutex_t mutex;
pthread_cond_t condition_variable;
int threads_required;
int threads_left;
unsigned int cycle;
} pthread_barrier_t;
The struct contains a mutex and a condition variable for synchronizing the cooperating threads. threads_required
is number of threads that must reach the barrier before all the threads will be allowed to continue. threads_left
is number of threads that have not reached the barrier yet. cycle
is the current cycle we are in.
Now, let’s implement pthread_barrier_init()
.
int pthread_barrier_init(pthread_barrier_t *barrier, void *attr, int count) {
barrier->threads_required = count;
barrier->threads_left = count;
barrier->cycle = 0;
pthread_mutex_init(&barrier->mutex, NULL);
pthread_cond_init(&barrier->condition_variable, NULL);
return 0;
}
In pthread_barrier_init
, we initialize the mutex and condition variable and initialize threads_required
and threads_left
to count
, cycle
to 0.
The real meat is in pthread_barrier_wait()
.
#define PTHREAD_BARRIER_SERIAL_THREAD 1
int pthread_barrier_wait(pthread_barrier_t *barrier) {
pthread_mutex_lock(&barrier->mutex);
if (--barrier->threads_left == 0) {
barrier->cycle++;
barrier->threads_left = barrier->threads_required;
pthread_cond_broadcast(&barrier->condition_variable);
pthread_mutex_unlock(&barrier->mutex);
return PTHREAD_BARRIER_SERIAL_THREAD;
} else {
unsigned int cycle = barrier->cycle;
while (cycle == barrier->cycle)
pthread_cond_wait(&barrier->condition_variable, &barrier->mutex);
pthread_mutex_unlock(&barrier->mutex);
return 0;
}
}
First, we lock the mutex to prevent other threads from accessing the structure at the same time. Then we check whether all threads have reached the barrier. If that is the case, we advance to the next cycle, reset threads_left
, and broadcast the barrier’s condition variable to wake up other threads. If some threads haven’t reached the barrier yet, we first remember which cycle we’re on. Then we wait until the barrier’s cycle changes using pthread_cond_wait()
. A pthread_cond_wait()
call must be governed by a while loop because spurious wake-ups can occur.
That’s all we need to write. Pretty easy, right?