Using condition variables

Condition variables allow threads to wait until some event or condition has occurred.

A condition variable has attributes that specify the characteristics of the condition. Typically, a program uses the following objects:
  • A boolean variable, indicating whether the condition is met
  • A mutex to serialize the access to the boolean variable
  • A condition variable to wait for the condition

Using a condition variable requires some effort from the programmer. However, condition variables allow the implementation of powerful and efficient synchronization mechanisms. For more information about implementing long locks and semaphores with condition variables, see Creating Complex Synchronization Objects.

When a thread is terminated, its storage may not be reclaimed, depending on an attribute of the thread. Such threads can be joined by other threads and return information to them. A thread that wants to join another thread is blocked until the target thread terminates. This joint mechanism is a specific case of condition-variable usage, the condition is the thread termination.

Condition attributes object

Like threads and mutexes, condition variables are created with the help of an attributes object. The condition attributes object is an abstract object, containing at most one attribute, depending on the implementation of POSIX options. It is accessed through a variable of type pthread_condattr_t. In AIX®, the pthread_condattr_t data type is a pointer; on other systems, it may be a structure or another data type.

Creating and destroying the condition attributes object

The condition attributes object is initialized to default values by the pthread_condattr_init subroutine. The attribute is handled by subroutines. The thread attributes object is destroyed by the pthread_condattr_destroy subroutine. This subroutine can release storage dynamically allocated by the pthread_condattr_init subroutine, depending on the implementation of the threads library.

In the following example, a condition attributes object is created and initialized with default values, then used and finally destroyed:
pthread_condattr_t attributes;
                /* the attributes object is created */
...
if (!pthread_condattr_init(&attributes)) {
                /* the attributes object is initialized */
        ...
                /* using the attributes object */
        ...
        pthread_condattr_destroy(&attributes);
                        /* the attributes object is destroyed */
}

The same attributes object can be used to create several condition variables. It can also be modified between two condition variable creations. When the condition variables are created, the attributes object can be destroyed without affecting the condition variables created with it.

Condition attribute

The following condition attribute is supported:

Process-shared
Specifies the process sharing of a condition variable. This attribute depends on the process sharing POSIX option.

Creating and destroying condition variables

A condition variable is created by calling the pthread_cond_init subroutine. You may specify a condition attributes object. If you specify a NULL pointer, the condition variable will have the default attributes. Thus, the following code fragment:
pthread_cond_t cond;
pthread_condattr_t attr;
...
pthread_condattr_init(&attr);
pthread_cond_init(&cond, &attr);
pthread_condattr_destroy(&attr);
is equivalent to the following:
pthread_cond_t cond;
...
pthread_cond_init(&cond, NULL);

The ID of the created condition variable is returned to the calling thread through the condition parameter. The condition ID is an opaque object; its type is pthread_cond_t. In AIX, the pthread_cond_t data type is a structure; on other systems, it may be a pointer or another data type.

A condition variable must be created once. Avoid calling the pthread_cond_init subroutine more than once with the same condition parameter (for example, in two threads concurrently executing the same code). Ensuring the uniqueness of a newly created condition variable can be done in the following ways:
  • Calling the pthread_cond_init subroutine prior to the creation of other threads that will use this variable; for example, in the initial thread.
  • Calling the pthread_cond_init subroutine within a one-time initialization routine. For more information, see One-Time Initializations.
  • Using a static condition variable initialized by the PTHREAD_COND_INITIALIZER static initialization macro; the condition variable will have default attributes.

After the condition variable is no longer needed, destroy it by calling the pthread_cond_destroy subroutine. This subroutine may reclaim any storage allocated by the pthread_cond_init subroutine. After having destroyed a condition variable, the same pthread_cond_t variable can be reused to create another condition. For example, the following code fragment is valid, although not very practical:

pthread_cond_t cond;
...
for (i = 0; i < 10; i++) {

        /* creates a condition variable */
        pthread_cond_init(&cond, NULL);

        /* uses the condition variable */

        /* destroys the condition */
        pthread_cond_destroy(&cond);
}

Like any system resource that can be shared among threads, a condition variable allocated on a thread's stack must be destroyed before the thread is terminated. The threads library maintains a linked list of condition variables; thus, if the stack where a mutex is allocated is freed, the list will be corrupted.

Using condition variables

A condition variable must always be used together with a mutex. A given condition variable can have only one mutex associated with it, but a mutex can be used for more than one condition variable. It is possible to bundle into a structure the condition, the mutex, and the condition variable, as shown in the following code fragment:
struct condition_bundle_t {
        int              condition_predicate;
        pthread_mutex_t  condition_lock;
        pthread_cond_t   condition_variable;
};

Waiting for a condition

The mutex protecting the condition must be locked before waiting for the condition. A thread can wait for a condition to be signaled by calling the pthread_cond_wait or pthread_cond_timedwait subroutine. The subroutine atomically unlocks the mutex and blocks the calling thread until the condition is signaled. When the call returns, the mutex is locked again.

The pthread_cond_wait subroutine blocks the thread indefinitely. If the condition is never signaled, the thread never wakes up. Because the pthread_cond_wait subroutine provides a cancelation point, the only way to exit this deadlock is to cancel the blocked thread, if cancelability is enabled. For more information, see Canceling a Thread.

The pthread_cond_timedwait subroutine blocks the thread only for a given period of time. This subroutine has an extra parameter, timeout, specifying an absolute date where the sleep must end. The timeout parameter is a pointer to a timespec structure. This data type is also called timestruc_t. It contains the following fields:

tv_sec
A long unsigned integer, specifying seconds
tv_nsec
A long integer, specifying nanoseconds
Typically, the pthread_cond_timedwait subroutine is used in the following manner:
struct timespec timeout;
...
time(&timeout.tv_sec);
timeout.tv_sec += MAXIMUM_SLEEP_DURATION;
pthread_cond_timedwait(&cond, &mutex, &timeout);

The timeout parameter specifies an absolute date. The previous code fragment shows how to specify a duration rather than an absolute date.

To use the pthread_cond_timedwait subroutine with an absolute date, you can use the mktime subroutine to calculate the value of the tv_sec field of the timespec structure. In the following example, the thread waits for the condition until 08:00 January 1, 2001, local time:
struct tm       date;
time_t          seconds;
struct timespec timeout;
...

date.tm_sec = 0;
date.tm_min = 0;
date.tm_hour = 8;
date.tm_mday = 1;
date.tm_mon = 0;         /* the range is 0-11 */
date.tm_year = 101;      /* 0 is 1900 */
date.tm_wday = 1;        /* this field can be omitted -
                            but it will really be a Monday! */
date.tm_yday = 0;        /* first day of the year */
date.tm_isdst = daylight;
        /* daylight is an external variable - we are assuming
           that Daylight Saving Time will still be used... */

seconds = mktime(&date);

timeout.tv_sec = (unsigned long)seconds;
timeout.tv_nsec = 0L;

pthread_cond_timedwait(&cond, &mutex, &timeout);

The pthread_cond_timedwait subroutine also provides a cancellation point, although the sleep is not indefinite. Thus, a sleeping thread can be canceled, whether or not the sleep has a timeout.

Signaling a condition

A condition can be signaled by calling either the pthread_cond_signal or the pthread_cond_broadcast subroutine.

The pthread_cond_signal subroutine wakes up at least one thread that is currently blocked on the specified condition. The awoken thread is chosen according to the scheduling policy; it is the thread with the most-favored scheduling priority (see Scheduling Policy and Priority). It may happen on multiprocessor systems, or some non-AIX systems, that more than one thread is awakened. Do not assume that this subroutine wakes up exactly one thread.

The pthread_cond_broadcast subroutine wakes up every thread that is currently blocked on the specified condition. However, a thread can start waiting on the same condition just after the call to the subroutine returns.

A call to these routines always succeeds, unless an invalid cond parameter is specified. This does not mean that a thread has been awakened. Furthermore, signaling a condition is not remembered by the library. For example, consider a condition C. No thread is waiting on this condition. At time t, thread 1 signals the condition C. The call is successful although no thread is awakened. At time t+1, thread 2 calls the pthread_cond_wait subroutine with C as cond parameter. Thread 2 is blocked. If no other thread signals C, thread 2 may wait until the process terminates.

You can avoid this kind of deadlock by checking the EBUSY error code returned by the pthread_cond_destroy subroutine when destroying the condition variable, as in the following code fragment:

The pthread_yield subroutine gives the opportunity to another thread to be scheduled; for example, one of the awoken threads. For more information about the pthread_yield subroutine.

The pthread_cond_wait and the pthread_cond_broadcast subroutines must not be used within a signal handler. To provide a convenient way for a thread to await a signal, the threads library provides the sigwait subroutine. For more information about the sigwait subroutine. For more information about the sigwait subroutine, see Signal Management.

Synchronizing threads with condition variables

while (pthread_cond_destroy(&cond) == EBUSY) {
        pthread_cond_broadcast(&cond);
        pthread_yield();
}
Condition variables are used to wait until a particular condition predicate becomes true. This condition predicate is set by another thread, usually the one that signals the condition.

Condition wait semantics

A condition predicate must be protected by a mutex. When waiting for a condition, the wait subroutine (either the pthread_cond_wait or pthread_cond_timedwait subroutine) atomically unlocks the mutex and blocks the thread. When the condition is signaled, the mutex is relocked and the wait subroutine returns. It is important to note that when the subroutine returns without error, the predicate may still be false.

The reason is that more than one thread may be awoken: either a thread called the pthread_cond_broadcast subroutine, or an unavoidable race between two processors simultaneously woke two threads. The first thread locking the mutex will block all other awoken threads in the wait subroutine until the mutex is unlocked by the program. Thus, the predicate may have changed when the second thread gets the mutex and returns from the wait subroutine.

In general, whenever a condition wait returns, the thread should reevaluate the predicate to determine whether it can safely proceed, should wait again, or should declare a timeout. A return from the wait subroutine does not imply that the predicate is either true or false.

It is recommended that a condition wait be enclosed in a "while loop" that checks the predicate. Basic implementation of a condition wait is shown in the following code fragment:
pthread_mutex_lock(&condition_lock);
while (condition_predicate == 0)
        pthread_cond_wait(&condition_variable, &condition_lock);
...
pthread_mutex_unlock(&condition_lock);

Timed wait semantics

When the pthread_cond_timedwait subroutine returns with the timeout error, the predicate may be true, due to another unavoidable race between the expiration of the timeout and the predicate state change.

Just as for non-timed wait, the thread should reevaluate the predicate when a timeout occurred to determine whether it should declare a timeout or should proceed anyway. It is recommended that you carefully check all possible cases when the pthread_cond_timedwait subroutine returns. The following code fragment shows how such checking could be implemented in a robust program:
int result = CONTINUE_LOOP;

pthread_mutex_lock(&condition_lock);
while (result == CONTINUE_LOOP) {
        switch (pthread_cond_timedwait(&condition_variable,
                &condition_lock, &timeout)) {

                case 0:
                if (condition_predicate)
                        result = PROCEED;
                break;

                case ETIMEDOUT:
                result = condition_predicate ? PROCEED : TIMEOUT;
                break;

                default:
                result = ERROR;
                break;
        }
}

...
pthread_mutex_unlock(&condition_lock);

The result variable can be used to choose an action. The statements preceding the unlocking of the mutex should be done as soon as possible because a mutex should not be held for long periods of time.

Specifying an absolute date in the timeout parameter allows easy implementation of real-time behavior. An absolute timeout need not be recomputed if it is used multiple times in a loop, such as that enclosing a condition wait. For cases where the system clock is advanced discontinuously by an operator, using an absolute timeout ensures that the timed wait will end as soon as the system time specifies a date later than the timeout parameter.

Condition variables usage example

The following example provides the source code for a synchronization point routine. A synchronization point is a given point in a program where different threads must wait until all threads (or at least a certain number of threads) have reached that point.

A synchronization point can simply be implemented by a counter, which is protected by a lock, and a condition variable. Each thread takes the lock, increments the counter, and waits for the condition to be signaled if the counter did not reach its maximum. Otherwise, the condition is broadcast, and all threads can proceed. The last thread that calls the routine broadcasts the condition.
#define SYNC_MAX_COUNT  10

void SynchronizationPoint()
{
        /* use static variables to ensure initialization */
        static mutex_t sync_lock = PTHREAD_MUTEX_INITIALIZER;
        static cond_t  sync_cond = PTHREAD_COND_INITIALIZER;
        static int sync_count = 0;

        /* lock the access to the count */
        pthread_mutex_lock(&sync_lock);

        /* increment the counter */
        sync_count++;

        /* check if we should wait or not */
        if (sync_count < SYNC_MAX_COUNT)

             /* wait for the others */
             pthread_cond_wait(&sync_cond, &sync_lock);

        else

            /* broadcast that everybody reached the point */
            pthread_cond_broadcast(&sync_cond);

        /* unlocks the mutex - otherwise only one thread
                will be able to return from the routine! */
        pthread_mutex_unlock(&sync_lock);
}

This routine has some limitations: it can be used only once, and the number of threads that call the routine is coded by a symbolic constant. However, this example shows a basic usage of condition variables. For more complex usage examples. For more complex usage examples, see Creating Complex Synchronization Objects.