mario::konrad
programming / C++ / sailing / nerd stuff
Multithreading: Tutorial 03: Condition Variable
© 2002 / Mario Konrad

What are Condition Variables?

Condition variables are a very elegant way to do some synchronisation. A thread can wait for a condition becoming true to continue and a thread can also make a condition come true. Since a thread is an active part, it can wait for or make a condition which are others waiting for.

Let’s have a look at an example (see figure 1 below). Thread X creates an item and stores it. Thread Y is waiting for the item to process it. Now let’s say: “thread X is making a condition for thread Y to get to work.”

We will have a look at an implementation of this example below.

The Program

This is the entire source code of the example. For a walkthrough, please read below.

The following program is a very simple producer-consumer demo. It shows two threads, one that produces some numbers, the second that consumes them. The demo is simple because we only use one place as buffer between those threads. So every time the producer has an item, it has to wait for the consumer to take it. To tell the consumer (sending a signal), that an item is ready we use the condition variable.

As soon as the ten items are produced and consumed, the threads will stop and the demo will end.

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

pthread_mutex_t mtx;
pthread_cond_t cond;

int how_many = 10;
int pool = 0;

void * producer(void * ptr)
{
    while (how_many > 0)
    {
        pthread_mutex_lock(&mtx);
        printf("producer: %d\n", how_many);
        pool = how_many;
        how_many--;
        pthread_mutex_unlock(&mtx);
        pthread_cond_signal(&cond);
    }
    pthread_exit(0);
}

void * consumer(void * ptr)
{
    while (how_many > 0)
    {
        pthread_mutex_lock(&mtx);
        pthread_cond_wait(&cond, &mtx);
        printf("consumer: %d\n", pool);
        pool = 0;
        pthread_mutex_unlock(&mtx);
    }
    pthread_exit(0);
}

int main(int argc, char ** argv)
{
    pthread_t prod, cons;
    pthread_mutex_init(&mtx, 0);
    pthread_cond_init(&cond, 0);
    pthread_create(&cons, 0, consumer, 0);
    pthread_create(&prod, 0, producer, 0);
    pthread_join(prod, 0);
    pthread_join(cons, 0);
    pthread_cond_destroy(&cond);
    pthread_mutex_destroy(&mtx);
    return 0;
}

Walkthrough

The first part is plain simple header inclusion.

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

Next we have to define some global variables. Global variables are not my favorite, but they will do for this example program. My usually advise: Do not use global data! Ok, the data we need is a mutex and a condition variable.

pthread_mutex_t mtx;
pthread_cond_t cond;

We also need some variables which take data for the simple producer-consumer demo.

int how_many = 10;
int pool = 0;

Next we’ll have a look at the consumer:

void * producer(void * ptr)
{
    while (how_many > 0)
    {
        pthread_mutex_lock(&mtx);
        printf("producer: %d\n", how_many);
        pool = how_many;
        how_many--;
        pthread_mutex_unlock(&mtx);

Nothing special so far. The thread locks the mutex to enter the critical section, prints out which item has been produced, stores it in the pool and decreases the total number of item to produce in the future. The last step is to unlock the mutex; to exit the critical section.

The new thing we’re looking at in this tutorial is the condition variable and their handling. So far we didn’t really used the condition variable at all, but now we have to send a signal that the pool is full:

        pthread_cond_signal(&cond);

What now follows is the usual thread termination statement.

    }
    pthread_exit(0);
}

Let’s have a look at the consumer.

void * consumer(void * ptr)
{
    while (how_many > 0)
    {
        pthread_mutex_lock(&mtx);

So far, there’s nothing special, locking the mutex to enter the critical section is standard procedure. Now appears the question, what if there aren’t any produced items yet? Here comes the answer: the condition variable that we are waiting for.

        pthread_cond_wait(&cond, &mtx);

This statement will unlock the mutex until the condition gets true, say a signal has been sent. Then the mutex gets locked again (using pthread_mutex_lock, so no timeout), and the thread continues into the critical section.

        printf("consumer: %d\n", pool);
        pool = 0;
        pthread_mutex_unlock(&mtx);
    }
    pthread_exit(0);
}

The rest of the function is quite normal: printing the data from the pool, resetting the pool and unlocking the mutex, exiting the critical section.

Last but not least, standard procedure to terminate the thread.

The function main is quite normal too. The only difference are the initialisation and the destruction of the condition variable.

Please note: the consumer thread is started first, so it can start to consume as soon as there are items ready. This is a measure of quality, not to run the producer first.

int main(int argc, char ** argv)
{
    pthread_t prod, cons;
    pthread_mutex_init(&mtx, 0);
    pthread_cond_init(&cond, 0);
    pthread_create(&cons, 0, consumer, 0);
    pthread_create(&prod, 0, producer, 0);
    pthread_join(prod, 0);
    pthread_join(cons, 0);
    pthread_cond_destroy(&cond);
    pthread_mutex_destroy(&mtx);
    return 0;
}

Exercises

Multithreading is fun, isn’t it? Go ahead try to implement a producer-consumer system:

Solutions

The solutions provided here are not the only one possible implementations. The are many solutions. This ones should give you a hint or if you like to compare your solution with this ones.

Walkthroughs are not provided by this site. Try to read them on your own.

Download Source Code

All source code files provided by this page is free to copy, modify, redistribute and use for any purpose. Use them on your own risk. The tutorial is copyrighted by Mario Konrad.