Multithreaded Programming Guide

Using Condition Variables

This section explains how to use condition variables. Table 4–5 lists the functions that are available.

Table 4–5 Condition Variables Functions

Operation 

Related Function Description 

Initialize a condition variable 

pthread_cond_init Syntax

Block on a condition variable 

pthread_cond_wait Syntax

Unblock a specific thread 

pthread_cond_signal Syntax

Block until a specified time 

pthread_cond_timedwait Syntax

Block for a specified interval 

pthread_cond_reltimedwait_np Syntax

Unblock all threads 

pthread_cond_broadcast Syntax

Destroy condition variable state 

pthread_cond_destroy Syntax

Initializing a Condition Variable

Use pthread_cond_init(3C) to initialize the condition variable pointed at by cv to its default value, or to specify condition variable attributes that are already set with pthread_condattr_init().

pthread_cond_init Syntax

int pthread_cond_init(pthread_cond_t *restrict cv,
          const pthread_condattr_t *restrict cattr);
#include <pthread.h>

pthread_cond_t cv;
pthread_condattr_t cattr;
int ret;

/* initialize a condition variable to its default value */
ret = pthread_cond_init(&cv, NULL);

/* initialize a condition variable */
ret = pthread_cond_init(&cv, &cattr); 

The effect of cattr set to NULL is the same as passing the address of a default condition variable attribute object, but without the memory overhead.

Use the macro PTHREAD_COND_INITIALIZER to initialize statically defined condition variables to their default attributes. The PTHREAD_COND_INITIALIZER macro has the same effect as dynamically allocating pthread_cond_init() with null attributes. No error checking is done.

Multiple threads must not simultaneously initialize or reinitialize the same condition variable. If a condition variable is reinitialized or is destroyed, the application must be sure that the condition variable is not in use.

pthread_cond_init Return Values

pthread_cond_init() returns zero after completing successfully. Any other return value indicates that an error occurred. When any of the following conditions occurs, the function fails and returns the corresponding value.


EINVAL

Description:

The value specified by cattr is invalid.


EBUSY

Description:

The condition variable is being used.


EAGAIN

Description:

The necessary resources are not available.


ENOMEM

Description:

Insufficient memory exists to initialize the condition variable.

Blocking on a Condition Variable

Use pthread_cond_wait(3C) to atomically release the mutex pointed to by mp and to cause the calling thread to block on the condition variable pointed to by cv.

pthread_cond_wait Syntax

int pthread_cond_wait(pthread_cond_t *restrict cv,pthread_mutex_t *restrict mutex);
#include <pthread.h>

pthread_cond_t cv;
pthread_mutex_t mp;
int ret;

/* wait on condition variable */
ret = pthread_cond_wait(&cv, &
mp); 

The blocked thread can be awakened by a pthread_cond_signal() , a pthread_cond_broadcast(), or when interrupted by delivery of a signal.

Any change in the value of a condition that is associated with the condition variable cannot be inferred by the return of pthread_cond_wait(). Such conditions must be reevaluated.

The pthread_cond_wait() routine always returns with the mutex locked and owned by the calling thread, even when returning an error.

This function blocks until the condition is signaled. The function atomically releases the associated mutex lock before blocking, and atomically acquires the mutex again before returning.

In typical use, a condition expression is evaluated under the protection of a mutex lock. When the condition expression is false, the thread blocks on the condition variable. The condition variable is then signaled by another thread when the thread changes the condition value. The change causes at least one thread that is waiting on the condition variable to unblock and to reacquire the mutex.

The condition that caused the wait must be retested before continuing execution from the point of the pthread_cond_wait(). The condition could change before an awakened thread reacquires the mutes and returns from pthread_cond_wait(). A waiting thread could be awakened spuriously. The recommended test method is to write the condition check as a while() loop that calls pthread_cond_wait().

    pthread_mutex_lock();
        while(condition_is_false)
            pthread_cond_wait();
    pthread_mutex_unlock();

The scheduling policy determines the order in which blocked threads are awakened. The default scheduling policy, SCHED_OTHER, does not specify the order in which threads are awakened. Under the SCHED_FIFO and SCHED_RR real-time scheduling policies, threads are awakened in priority order.


Note –

pthread_cond_wait() is a cancellation point. If a cancel is pending and the calling thread has cancellation enabled, the thread terminates and begins executing its cleanup handlers while continuing to hold the lock.


pthread_cond_wait Return Values

pthread_cond_wait() returns zero after completing successfully. Any other return value indicates that an error occurred. When the following condition occurs, the function fails and returns the corresponding value.


EINVAL

Description:

The value specified by cv or mp is invalid.

Unblocking One Thread

Use pthread_cond_signal(3C) to unblock one thread that is blocked on the condition variable pointed to by cv.

pthread_cond_signal Syntax

int pthread_cond_signal(pthread_cond_t *cv);
#include <pthread.h>

pthread_cond_t cv;
int ret;

/* one condition variable is signaled */
ret = pthread_cond_signal(&cv); 

Modify the associated condition under the protection of the same mutex used with the condition variable being signaled. Otherwise, the condition could be modified between its test and blocking in pthread_cond_wait(), which can cause an infinite wait.

The scheduling policy determines the order in which blocked threads are awakened. The default scheduling policy, SCHED_OTHER, does not specify the order in which threads are awakened. Under the SCHED_FIFO and SCHED_RR real-time scheduling policies, threads are awakened in priority order.

When no threads are blocked on the condition variable, calling pthread_cond_signal() has no effect.


Example 4–8 Using pthread_cond_wait() and pthread_cond_signal()

pthread_mutex_t count_lock;
pthread_cond_t count_nonzero;
unsigned count;

decrement_count()
{
    pthread_mutex_lock(&count_lock);
    while (count == 0)
        pthread_cond_wait(&count_nonzero, &count_lock);
    count = count - 1;
    pthread_mutex_unlock(&count_lock);
}

increment_count()
{
    pthread_mutex_lock(&count_lock);
    if (count == 0)
        pthread_cond_signal(&count_nonzero);
    count = count + 1;
    pthread_mutex_unlock(&count_lock);
}

pthread_cond_signal Return Values

pthread_cond_signal() returns zero after completing successfully. Any other return value indicates that an error occurred. When the following condition occurs, the function fails and returns the corresponding value.


EINVAL

Description:

cv points to an illegal address.

Example 4–8 shows how to use pthread_cond_wait() and pthread_cond_signal().

Blocking Until a Specified Time

Use pthread_cond_timedwait(3C) as you would use pthread_cond_wait(), except that pthread_cond_timedwait() does not block past the time of day specified by abstime .

pthread_cond_timedwait Syntax

int pthread_cond_timedwait(pthread_cond_t *restrict cv,
          pthread_mutex_t *restrict mp, 
          const struct timespec *restrict abstime);
#include <pthread.h>
#include <time.h>

pthread_cond_t cv;
pthread_mutex_t mp;
timestruct_t abstime;
int ret;

/* wait on condition variable */
ret = pthread_cond_timedwait(&cv, &
mp, &abstime); 

pthread_cond_timedwait() always returns with the mutex locked and owned by the calling thread, even when pthread_cond_timedwait() is returning an error.

The pthread_cond_timedwait() function blocks until the condition is signaled or until the time of day specified by the last argument has passed.


Note –

pthread_cond_timedwait() is also a cancellation point.



Example 4–9 Timed Condition Wait

pthread_timestruc_t to;
pthread_mutex_t m;
pthread_cond_t c;
...
pthread_mutex_lock(&m);
clock_gettime(CLOCK_REALTIME, &to);
to.tv_sec += TIMEOUT;
while (cond == FALSE) {
    err = pthread_cond_timedwait(&c, &m, &to);
    if (err == ETIMEDOUT) {
        /* timeout, do something */
        break;
    }
}
pthread_mutex_unlock(&m);

pthread_cond_timedwait Return Values

pthread_cond_timedwait() returns zero after completing successfully. Any other return value indicates that an error occurred. When either of the following conditions occurs, the function fails and returns the corresponding value.


EINVAL

Description:

cv, mp, or abstime points to an illegal address.


EINVAL

Description:

Different mutexes were supplied for concurrent pthread_cond_timedwait() operations on the same condition variable.


ETIMEDOUT

Description:

The time specified by abstime has passed.


EPERM

Description:

The mutex was not owned by the current thread at the time of the call.

The timeout is specified as a time of day so that the condition can be retested efficiently without recomputing the value, as shown in Example 4–9.

Blocking For a Specified Interval

Use pthread_cond_reltimedwait_np(3C) as you would use pthread_cond_timedwait() with one exception. pthread_cond_reltimedwait_np() takes a relative time interval rather than an absolute future time of day as the value of its last argument.

pthread_cond_reltimedwait_np Syntax

int pthread_cond_reltimedwait_np(pthread_cond_t *cv, 
           pthread_mutex_t *mp, 
          const struct timespec *reltime);
#include <pthread.h>
#include <time.h> 
pthread_cond_t cv; 
pthread_mutex_t mp; 
timestruct_t reltime; 
int ret; 

/* wait on condition variable */ 
ret = pthread_cond_reltimedwait_np(&cv, &mp, &reltime); 

pthread_cond_reltimedwait_np() always returns with the mutex locked and owned by the calling thread, even when pthread_cond_reltimedwait_np() is returning an error. The pthread_cond_reltimedwait_np() function blocks until the condition is signaled or until the time interval specified by the last argument has elapsed.


Note –

pthread_cond_reltimedwait_np() is also a cancellation point.


pthread_cond_reltimedwait_np Return Values

pthread_cond_reltimedwait_np() returns zero after completing successfully. Any other return value indicates that an error occurred. When either of the following conditions occurs, the function fails and returns the corresponding value.


EINVAL

Description:

The value specified by reltime is invalid.


ETIMEDOUT

Description:

The time interval specified by reltime has passed.

Unblocking All Threads

Use pthread_cond_broadcast(3C) to unblock all threads that are blocked on the condition variable pointed to by cv, specified by pthread_cond_wait().

pthread_cond_broadcast Syntax

int pthread_cond_broadcast(pthread_cond_t *cv);
#include <pthread.h>

pthread_cond_t cv;
int ret;

/* all condition variables are signaled */
ret = pthread_cond_broadcast(&cv); 

When no threads are blocked on the condition variable, pthread_cond_broadcast() has no effect.

Since pthread_cond_broadcast() causes all threads blocked on the condition to contend again for the mutex lock, use pthread_cond_broadcast() with care. For example, use pthread_cond_broadcast() to allow threads to contend for varying resource amounts when resources are freed, as shown in Example 4–10.


Example 4–10 Condition Variable Broadcast

pthread_mutex_t rsrc_lock;
pthread_cond_t rsrc_add;
unsigned int resources;

get_resources(int amount)
{
    pthread_mutex_lock(&rsrc_lock);
    while (resources < amount) {
        pthread_cond_wait(&rsrc_add, &rsrc_lock);
    }
    resources -= amount;
    pthread_mutex_unlock(&rsrc_lock);
}

add_resources(int amount)
{
    pthread_mutex_lock(&rsrc_lock);
    resources += amount;
    pthread_cond_broadcast(&rsrc_add);
    pthread_mutex_unlock(&rsrc_lock);
}

Note that in add_resources() whether resources are updated first, or if pthread_cond_broadcast() is called first inside the mutex lock does not matter.

Modify the associated condition under the protection of the same mutex that is used with the condition variable being signaled. Otherwise, the condition could be modified between its test and blocking in pthread_cond_wait(), which can cause an infinite wait.

pthread_cond_broadcast Return Values

pthread_cond_broadcast() returns zero after completing successfully. Any other return value indicates that an error occurred. When the following condition occurs, the function fails and returns the corresponding value.


EINVAL

Description:

cv points to an illegal address.

Destroying the Condition Variable State

Use pthread_cond_destroy(3C) to destroy any state that is associated with the condition variable pointed to by cv.

pthread_cond_destroy Syntax

int pthread_cond_destroy(pthread_cond_t *cv);
#include <pthread.h>

pthread_cond_t cv;
int ret;

/* Condition variable is destroyed */
ret = pthread_cond_destroy(&cv); 

Note that the space for storing the condition variable is not freed.

pthread_cond_destroy Return Values

pthread_cond_destroy() returns zero after completing successfully. Any other return value indicates that an error occurred. When the following condition occurs, the function fails and returns the corresponding value.


EINVAL

Description:

The value specified by cv is invalid.

Lost Wake-Up Problem

A call to pthread_cond_signal() or pthread_cond_broadcast() when the thread does not hold the mutex lock associated with the condition can lead to lost wake-up bugs.

A lost wake-up occurs when all of the following conditions are in effect:

This can occur only if the condition being tested is modified without holding the mutex lock associated with the condition. As long as the condition being tested is modified only while holding the associated mutex, pthread_cond_signal() and pthread_cond_broadcast() can be called regardless of whether they are holding the associated mutex.

Producer and Consumer Problem

The producer and consumer problem is one of the small collection of standard, well-known problems in concurrent programming. A finite-size buffer and two classes of threads, producers and consumers, put items into the buffer (producers) and take items out of the buffer (consumers).

A producer cannot put something in the buffer until the buffer has space available. A consumer cannot take something out of the buffer until the producer has written to the buffer.

A condition variable represents a queue of threads that wait for some condition to be signaled.

Example 4–11 has two such queues. One (less) queue for producers waits for a slot in the buffer. The other (more) queue for consumers waits for a buffer slot containing information. The example also has a mutex, as the data structure describing the buffer must be accessed by only one thread at a time.


Example 4–11 Producer and Consumer Problem With Condition Variables

typedef struct { 
     char buf[BSIZE]; 
     int occupied; 
     int nextin; 
     int nextout; 
     pthread_mutex_t mutex; 
     pthread_cond_t more; 
     pthread_cond_t less; } 
buffer_t; 

buffer_t buffer;

As Example 4–12 shows, the producer thread acquires the mutex protecting the buffer data structure. The producer thread then makes certain that space is available for the item produced. If space is not available, the producer thread calls pthread_cond_wait() . pthread_cond_wait() causes the producer thread to join the queue of threads that are waiting for the condition less to be signaled. less represents available room in the buffer.

At the same time, as part of the call to pthread_cond_wait(), the thread releases its lock on the mutex. The waiting producer threads depend on consumer threads to signal when the condition is true, as shown in Example 4–12. When the condition is signaled, the first thread waiting on less is awakened. However, before the thread can return from pthread_cond_wait(), the thread must acquire the lock on the mutex again.

Acquire the mutex to ensure that the thread again has mutually exclusive access to the buffer data structure. The thread then must check that available room in the buffer actually exists. If room is available, the thread writes into the next available slot.

At the same time, consumer threads might be waiting for items to appear in the buffer. These threads are waiting on the condition variable more . A producer thread, having just deposited something in the buffer, calls pthread_cond_signal() to wake up the next waiting consumer. If no consumers are waiting, this call has no effect.

Finally, the producer thread unlocks the mutex, allowing other threads to operate on the buffer data structure.


Example 4–12 The Producer and Consumer Problem: the Producer

void producer(buffer_t *b, char item)
{
    pthread_mutex_lock(&b->mutex);
   
    while (b->occupied >= BSIZE)
        pthread_cond_wait(&b->less, &b->mutex);

    assert(b->occupied < BSIZE);

    b->buf[b->nextin++] = item;

    b->nextin %= BSIZE;
    b->occupied++;

    /* now: either b->occupied < BSIZE and b->nextin is the index
       of the next empty slot in the buffer, or
       b->occupied == BSIZE and b->nextin is the index of the
       next (occupied) slot that will be emptied by a consumer
       (such as b->nextin == b->nextout) */

    pthread_cond_signal(&b->more);

    pthread_mutex_unlock(&b->mutex);
}

Note the use of the assert() statement. Unless the code is compiled with NDEBUG defined, assert() does nothing when its argument evaluates to true (nonzero). The program aborts if the argument evaluates to false (zero). Such assertions are especially useful in multithreaded programs. assert() immediately points out runtime problems if the assertion fails. assert() has the additional effect of providing useful comments.

The comment that begins /* now: either b->occupied ... could better be expressed as an assertion, but the statement is too complicated as a Boolean-valued expression and so is given in English.

Both assertions and comments are examples of invariants. These invariants are logical statements that should not be falsified by the execution of the program with the following exception. The exception occurs during brief moments when a thread is modifying some of the program variables mentioned in the invariant. An assertion, of course, should be true whenever any thread executes the statement.

The use of invariants is an extremely useful technique. Even if the invariants are not stated in the program text, think in terms of invariants when you analyze a program.

The invariant in the producer code that is expressed as a comment is always true whenever a thread executes the code where the comment appears. If you move this comment to just after the mutex_unlock(), the comment does not necessarily remain true. If you move this comment to just after the assert() , the comment is still true.

This invariant therefore expresses a property that is true at all times with the following exception. The exception occurs when either a producer or a consumer is changing the state of the buffer. While a thread is operating on the buffer under the protection of a mutex, the thread might temporarily falsify the invariant. However, once the thread is finished, the invariant should be true again.

Example 4–13 shows the code for the consumer. The logic flow is symmetric with the logic flow of the producer.


Example 4–13 The Producer and Consumer Problem: the Consumer

char consumer(buffer_t *b)
{
    char item;
    pthread_mutex_lock(&b->mutex);
    while(b->occupied <= 0)
        pthread_cond_wait(&b->more, &b->mutex);

    assert(b->occupied > 0);

    item = b->buf[b->nextout++];
    b->nextout %= BSIZE;
    b->occupied--;

    /* now: either b->occupied > 0 and b->nextout is the index
       of the next occupied slot in the buffer, or
       b->occupied == 0 and b->nextout is the index of the next
       (empty) slot that will be filled by a producer (such as
       b->nextout == b->nextin) */

    pthread_cond_signal(&b->less);
    pthread_mutex_unlock(&b->mutex);

    return(item);
}