[ Previous | Next | Contents | Glossary | Home | Search ]
AIX Version 4.3 General Programming Concepts: Writing and Debugging Programs

Using Condition Variables

Condition variables allow threads to wait until some event or condition has occurred. Typically, a program will use three objects:

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

A condition variable has attributes, which specify the characteristics of the condition. In the current version of AIX, the condition attributes are not used. Therefore, the condition attributes object can be ignored when creating a condition variable.

Read the following to learn more about using condition variables:

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.

Condition Attributes Object Creation and Destruction

The mutex 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 may free storage dynamically allocated by the pthread_condattr_init subroutine, depending on the implementation of the threads library.

In the following example, a mutex 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

In AIX, no condition attribute is defined. Condition attributes depend on POSIX options that are not implemented in AIX . However, the following attribute may be defined on other systems:

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

See Advanced Attributes for more information about the process-shared attribute.

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 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:

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. Calling the pthread_cond_init subroutine more than once with the same condition parameter (for example, in two threads concurrently executing the same code) should be avoided. The second call will fail, returning an EBUSY error code. Ensuring the uniqueness of a condition variable creation can be done in three ways:

Once the condition variable is no longer needed, it should be destroyed 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 for creating another condition. For example, the following code fragment is legal, although not very realistic:

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. The same mutex must be used for the same condition variable, even for different threads. It is possible to bundle in 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;
};

See Synchronizing Threads with Condition Variables for more information about using the condition predicate.

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 cancellation point, the only way to get out of this deadlock is to cancel the blocked thread, if cancelability is enabled.

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 two 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 pthread_cond_timedwait 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 will wait for the condition until 08:00 January 1, 2001, local time (that is a long sleep, indeed!):

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 savings 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 the sleep has a timeout or not.

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. It may happen on multiprocessor systems, or some non-AIX systems, that more than one thread is woken up. 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 woken up. 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.

A way to avoid this kind of deadlock is to check the EBUSY error code returned by the pthread_cond_destroy subroutine when destroying the condition variable, as in the following code fragment:

while (pthread_cond_destroy(&cond) == EBUSY) {
        pthread_cond_broadcast(&cond);
        pthread_yield();
}

The pthread_yield subroutine gives the opportunity to another thread to be scheduled, one of the awoken threads for example. See Threads Scheduling 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. See Signal Management for more information about the sigwait subroutine.

Synchronizing Threads with Condition Variables

Condition variables are used to wait until a particular predicate becomes true. This predicate is set by another thread, usually the one that signals the condition.

Condition Wait Semantics

A predicate must be protected by a mutex. When waiting for a condition, the wait subroutine (either pthread_cond_wait or pthread_cond_timedwait) 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 re-evaluate 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. The following code fragment provides a basic implementation of a condition wait.

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. This is 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 re-evaluate the predicate when a timeout occurred to determine whether it should declare a timeout or should proceed anyway. It is recommended to 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 as quick 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 does not need to 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 calling 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 will call the routine is coded by a symbolic constant. However, this example shows a basic usage of condition variables. More complex usage can be found in Making Complex Synchronization Objects.

Related Information

Thread Programming Concepts.

Synchronization Overview.

Using Mutexes.

Joining Threads.

List of Synchronization Subroutines.

Threads Library Options.


[ Previous | Next | Contents | Glossary | Home | Search ]