Using condition variables
Condition variables allow threads to wait until some event or condition has occurred.
- 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.
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
pthread_cond_t cond;
pthread_condattr_t attr;
...
pthread_condattr_init(&attr);
pthread_cond_init(&cond, &attr);
pthread_condattr_destroy(&attr);
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.
- 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
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
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.
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.
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.
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.
#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.