A Barrier in computing is a synchronization method where a group of threads cannot proceed until a condition allows the blocked threads to proceed. In this post we will construe an example of a Barrier that relates to men and women at the dinner table using pthreads. We will have a bunch of men waiting to get their food, but the men will be blocked until the woman has eaten, thus, trigger the start state of the Barrier.

Barrier Pthread Example: Ladies First!

Let’s outline the following case for our Barrier. Ladies are first so all the men must wait until the woman has eaten.

  • We have 2 men pthreads and 1 woman pthread
  • The men will wait until the woman has eaten - implicitly the men pthreads must start before the woman
  • Once the woman has eaten the men are free to eat
  • Men will eat and the program will end

Relation of the Barrier Example in pthreads

Since a Barrier needs synchronization and in pthreads the fundamental building block for synchronization is the mutex. We will use the mutex for our example. We will also use another tool in the pthread library called the condition variable.

Distinction between a pthread Mutex and Condition variable

The mutex in the pthread library lets a thread synchronize on data. The mutex will block while a thread works on data. The condition variable is a step further where it allows threads to synchornize on the value of the data. Think of them as a one-to-many relationship - for a single mutex you could have mutiple condition variables. For the purposes of this exercise we’ll have one mutex which relates to a single condition variable. Having the same condition variable map to multiple mutex variables wouldn’t be good. I’ll quote the pthread_cond_wait man pages. “The effect of using more than one mutex for concurrent pthread_cond_wait() or pthread_cond_timedwait() operations on the same condition variable is undefined; that is, a condition variable becomes bound to a unique mutex when a thread waits on the condition variable, and this (dynamic) binding ends when the wait returns.”

Relating the Barrier Example to - Ladies First

In the Ladies First Barrier example we have the following synchronization details:

  • 2 pthreads for the men, 1 pthread for the woman
  • The men pthreads will have to go first - more on this later
  • The men will wait on a condition variable called men_can_go_now_cond
  • The woman, once done eating will broadcast to all men waiting on the condition variable
  • Underlying this will be a mutex called food_mutex

Ladies First Barrier Example in C using Pthreads

We’ve gotten a lot of background already, at this point let’s get straight to our example in c using pthreads.

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

void* woman_dinner(void *data);
void*   man_dinner(void *data);

pthread_mutex_t food_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t men_can_go_now_cond = PTHREAD_COND_INITIALIZER;
int thread_ids[3] = {0,1,2};

int
main(int argc, char *argv[])
{
  pthread_t threads[3];

  pthread_create(&threads[1], NULL,   man_dinner, &thread_ids[1]);
  pthread_create(&threads[2], NULL,   man_dinner, &thread_ids[2]);

  // a condition so that by the time the woman thread starts
  // the men threads have already started and are waiting
  // on the condition
  sleep(1);
  pthread_create(&threads[0], NULL, woman_dinner, &thread_ids[0]);

  for(int i=0;i<3;i++)
  {
    pthread_join(threads[i], NULL);
    printf("thread %i joined\n",i);
  }

  return 0;
}

void* woman_dinner(void *data)
{
  int *id = (int *) data;

  printf("woman %d - letting the men eat - waiting on food mutex\n",*id);
  pthread_mutex_lock(&food_mutex);
  printf("woman %d - letting the men eat - got food mutex - broadcast\n",*id);
  pthread_cond_broadcast(&men_can_go_now_cond);
  printf("woman %d - unlocking mutex\n",*id);
  pthread_mutex_unlock(&food_mutex);
  printf("woman %d - unlocked mutex\n",*id);

  return NULL;
}

void* man_dinner(void *data)
{
  int *id = (int *) data;

  printf("man %d waiting on a woman - waiting on food mutex\n",*id);
  pthread_mutex_lock(&food_mutex);
  printf("man %d waiting on a woman - got food mutex - waiting on condition\n",*id);
  pthread_cond_wait(&men_can_go_now_cond,&food_mutex);
  printf("man %d eating - got condition - unlocking mutex\n",*id);
  pthread_mutex_unlock(&food_mutex);
  printf("man %d eating after the lady - mutex unlocked\n",*id);

  return NULL;
}

When this program is run the following output was captured. These are pthreads so we cannot guarantee execution order can be different each time the program is run.

$ ./src/ladies_first
man 2 waiting on a woman - waiting on food mutex
man 2 waiting on a woman - got food mutex - waiting on condition
man 1 waiting on a woman - waiting on food mutex
man 1 waiting on a woman - got food mutex - waiting on condition
woman 0 - waiting on food mutex
woman 0 - letting the men eat - got food mutex - broadcast
woman 0 - unlocking mutex
man 2 eating - got condition - unlocking mutex
man 2 ate after the lady - mutex unlocked
man 1 eating - got condition - unlocking mutex
man 1 ate after the lady - mutex unlocked
woman 0 - unlocked mutex
thread 0 joined
thread 1 joined
thread 2 joined

From the output above we can see the the 2 men pthreads will obtain the food mutex and then wait on the condition variable. The delay happens and then the woman comes in and get’s the food mutex right away. She then broadcasts out to all the men pthreads waiting on the food mutex and they get to eat. Pay some close attention to the mutex and how they are locked and unlocked.

The finer details of this Barrier example

There are a couple of fine points to be made here in this Barrier example.

The first and biggest to me is not a fine detail, but a major detail. When the thread hits the pthread_cond_wait() function which MUST take a locked mutex AND a condition variable, something special happens. What happens is the mutex is the mutex is released. This can be seen in the output section of the example where the men pthreads get the food mutex and then wait for the condition. The man #2 and #1 both are waiting on the condition and from the print’s above are passed the mutex without it being unlocked. See the pthread_cond_wait man pages for more on this.

The second point to be made is the sleep we’ve inserted. This is needed because we need the mean blocked on the condition variable before the woman broadcasts to them. This functionality is for the example and is quite crude.

Where to go from here

If this example is useful please say so in the comments below! This code could be much more robust by actually checking return codes from the pthread library calls which set the error number. The sleep call is crude. In a real world scenario we’d need to consider signals which adds another layer of complexity.

The last part is we don’t consider destroying our mutex and condition variable.

References