A condvar is not a semaphore …

I’ve been running into some situations lately where developers have been using condition variables (the pthread_cond_* family of functions) as semaphores (the sem_* family of functions) … but instead introducing bugs into their programs.The confusion is somewhat understandable. Given that condition variables have a wait() and a signal() function similar to the wait() and post()  function available for semaphores, the two might seem interchangeable.Here is an example of a condvar being used improperly as a semaphore replacement:

 struct _data {
  pthread_mutex_t m;
  pthread_condvar_t c;
  ... other stuff ...
 };
void * writer_thread(void *arg) {
  struct _data *d = (struct _data *)arg;
  ...
  pthread_mutex_lock(&d->m);
  pthread_cond_signal(&d->c);
  pthread_mutex_unlock(&d->m);
  ...
 }
void * reader_thread(void *arg) {
  struct _data *d = (struct _data *)arg;
  ...
  pthread_mutex_lock(&d->m);
  pthread_cond_wait(&d->c, &d->m);
  pthread_mutex_unlock(&d->m);
  ...
 }

The _intention_ here is that the writer_thread is signaling to the reader_thread that things are good and it can continue to operate. If this code were using sem_post()/sem_wait() the code would like operate as intended (assuming proper initialization values).However, imagine the following execution sequence (cause by context switches):

writer_thread:pthread_mutex_lock()
reader_thread:pthread_mutex_lock() > blocks (mutex held)
writer_thread:pthread_cond_signal()
writer_thread:pthread_mutex_unlock()
reader_thread:pthread_cond_wait() > blocks waiting
...

In this case the writer thread had signaled the reader thread, but since the reader thread was not yet blocked, that signalling is lost. In the best case the writer_thread will have to wait a few extra cycles until the next reader_thread update or in the worst case the writer_thread may block forever if there are no further updates.

Condition variables are like semaphores on steroids. You can trigger off of much more sophisticated combinations of data, but at the cost of managing those triggers and that data yourself.

Assuming that we want our example to respond to every written request, and behave like a counting semaphore, then we should modify it as follows:

struct _data {
  pthread_mutex_t m;
  pthread_condvar_t c;
  int data;
  ... other stuff ...
};  

void * writer_thread(void *arg) {
  struct _data *d = (struct _data *)arg;
  ...
  pthread_mutex_lock(&d->m);
  d->data++;
  pthread_cond_signal(&d->c);
  pthread_mutex_unlock(&d->m);
   ...
}  

void * reader_thread(void *arg) {
  struct _data *d = (struct _data *)arg;
  ...
  pthread_mutex_lock(&d->m);
   while(d->data <= 0) {
  pthread_cond_wait(&d->c, &d->m);
  }
  d->data--;
  pthread_mutex_unlock(&d->m);
  ...
 }

As you can see, the state data management is more manual, but is entirely within your control. In this case we are triggering on every data increment, but it can be easily adjusted to trigger on every fifth data increment instead.

void * reader_thread(void *arg) {
  ...
  while(d->data <= 5) {
  pthread_cond_wait(&d->c, &d->m);
  }
  d->data -= 5;
  ...

Code that uses a condvar in a lock/wait/unlock scenario without any intermediate condition testing being done around the wait should always be viewed with suspicion since chances are it was once a semaphore implementation that was switched to using condvars.

 If you wanted to give this code a bit of an additional performance boost, you could also avoid the (little) thundering herd problem that is introduced here by moving the condvar signalling operation of the writer thread to occur outside of the mutex locked area.  This will save the reader thread from waking up from the condition variable, only to go mutex blocked until the mutex is given up.

3 comments so far

  1. [...] flexibility and potentially higher throughput. If that is the case, reading through the post a condvar is not a semaphore might be usefull. *Named semaphores, normal semaphores and mutexes can all be used as [...]

  2. Ну и почему это только так? Я считаю, почему не расширить данную тему.

  3. Объясните почему только так? Сомневаюсь, как можно прояснить этот обзор.


Leave a reply