Next Previous Contents

18. condition variable

We often have the case that we require one thread to wait until the other thread completes something. We can write such a code with condition variable. One thread has a role to wake up the other thread. The other thread has a role to wait(sleep) until it's notified(signalled). Sometimes, the former is called producer and the latter is called consumer. Here, I call them so.

At first, we create a condition variable object. We just call apr_thread_cond_create() as follows:

/* excerpted from thread- cond-sample.c */

typedef struct {
    /* condition variable should be used with a mutex variable */
    apr_thread_mutex_t *mutex;
    apr_thread_cond_t  *cond;

    /* shared context depends on application */
    int input_num;
} my_production_t;

apr_thread_mutex_create(&prod.mutex, APR_THREAD_MUTEX_UNNESTED, mp);
apr_thread_cond_create(&prod.cond, mp);

As you can see, I create apr_thread_mutex_t object, too. That's because condition variable requires apr_thread_mutex_t. The relation between two objects is not explicit in their creations, but it will be clear when we call apr_thread_cond_wait() later.

Let's take a look at thread- cond-sample.c. In which, the main thread works as a producer and the sub thread works as a consumer.

The basic structure of the producer's code looks as follows:

/* producer thread's basic code */
apr_thread_mutex_lock(prod->mutex);
apr_thread_cond_signal(prod->cond);
apr_thread_mutex_unlock(prod->mutex);

By calling apr_thread_cond_signal(), producer thread can wake up consumer thread. The call must be protected by the associated mutex lock, because the condition variable object is shared between producer and consumer. Calling apr_thread_cond_signal() never block. Even if there are multiple consumers waiting, only one consumer is waken up. If we want to wake all of them, we use a call to apr_thread_cond_broadcast().

The basic structure of the consumer's code looks as follows:

/* consumer thread's basic code */
apr_thread_mutex_lock(prod->mutex);
apr_thread_cond_wait(prod->cond, prod->mutex);
apr_thread_mutex_unlock(prod->mutex);

By calling apr_thread_cond_wait(), the consumer thread blocks. It sleeps until producer thread wakes it up. If we want a timeout for the wait, we can use apr_thread_cond_timedwait() instead.

As same as producer, calling apr_thread_cond_wait() must be protected by the associated mutex lock. Moreover, apr_thread_cond_wait() requires the mutex lock as the function's second argument. The reason is that apr_thread_cond_wait() internaly releases(unlocks) the associted mutex lock, then sleeps. After waken up, it internally acquires the mutex lock, again. At first glance, both producer's code and consumer's code are protected by the same mutex lock, so that you would think they don't work properly. However, it works by such internal unlock of apr_thread_cond_wait().

If producer calls apr_thread_cond_signal() while no consumer exists, what happens? Unfortunately, there is a portability issue. Think about the case that a producer thread calls apr_thread_cond_signal() when no consumer thread waits for the condition variable. Then, a consumer thread calls apr_thread_cond_wait() for the condition variable. On Unix the consumer sleeps, but on Windows the consumer thread doesn't sleep. To get around this cross-platform problem, we generally must have a flag variable. Please take a look at thread- cond-sample.c about it. my_production_t::input_num works as such a flag, although it is also an output production shared between two threads. Without such a flag, apr_thread_cond_wait() would sleep forever.


Next Previous Contents