13.4 Condition Variables

Team-FLY

13.4 Condition Variables

Consider the problem of having a thread wait until some arbitrary condition is satisfied. For concreteness, assume that two variables, x and y , are shared by multiple threads. We want a thread to wait until x and y are equal. A typical incorrect busy-waiting solution is

 while (x != y) ; 

Having a thread use busy waiting is particularly troublesome . Depending on how the threads are scheduled, the thread doing the busy waiting may prevent other threads from ever using the CPU, in which case x and y never change. Also, access to shared variables should always be protected.

Here is the correct strategy for non-busy waiting for the predicate x==y to become true.

  1. Lock a mutex.

  2. Test the condition x==y .

  3. If true, unlock the mutex and exit the loop.

  4. If false, suspend the thread and unlock the mutex.

The mutex must be held until a test determines whether to suspend the thread. Holding the mutex prevents the condition x==y from changing between the test and the suspension of the thread. The mutex needs to be unlocked while the thread is suspended so that other threads can access x and y . The strategy assumes that the code protects all other access to the shared variables x and y with the mutex.

Applications manipulate mutex queues through well-defined system library functions such as pthread_mutex_lock and pthread_mutex_unlock . These functions are not sufficient to implement (in a simple manner) the queue manipulations required here. We need a new data type, one associated with a queue of processes waiting for an arbitrary condition such as x==y to become true. Such a data type is called a condition variable .

A classical condition variable is associated with a particular condition. In contrast, POSIX condition variables provide an atomic waiting mechanism but are not associated with particular conditions.

The function pthread_cond_wait takes a condition variable and a mutex as parameters. It atomically suspends the calling thread and unlocks the mutex. It can be thought of as placing the thread in a queue of threads waiting to be notified of a change in a condition. The function returns with the mutex reacquired when the thread receives a notification. The thread must test the condition again before proceeding.

Example 13.13

The following code segment illustrates how to wait for the condition x==y , using a POSIX condition variable v and a mutex m .

 pthread_mutex_lock(&m); while (x != y)    pthread_cond_wait(&v, &m); /* modify x or y if necessary */ pthread_mutex_unlock(&m); 

When the thread returns from pthread_cond_wait it owns m , so it can safely test the condition again. The code segment omits error checking for clarity.

The function pthread_cond_wait should be called only by a thread that owns the mutex, and the thread owns the mutex again when the function returns . The suspended thread has the illusion of uninterrupted mutex ownership because it owns the mutex before the call to pthread_cond_wait and owns the mutex when pthread_cond_wait returns. In reality, the mutex can be acquired by other threads during the suspension.

A thread that modifies x or y can call pthread_cond_signal to notify other threads of the change. The pthread_cond_signal function takes a condition variable as a parameter and attempts to wake up at least one of the threads waiting in the corresponding queue. Since the blocked thread cannot return from pthread_cond_wait without owning the mutex, pthread_cond_signal has the effect of moving the thread from the condition variable queue to the mutex queue.

Example 13.14

The following code might be used by another thread in conjunction with Example 13.13 to notify the waiting thread that it has incremented x .

 pthread_mutex_lock(&m); x++; pthread_cond_signal(&v); pthread_mutex_unlock(&m); 

The code segment omits error checking for clarity.

In Example 13.14, the caller holds the mutex while calling pthread_cond_signal . POSIX does not require this to be the case, and the caller could have unlocked the mutex before signaling. In programs that have threads of different priorities, holding the mutex while signaling can prevent lower priority threads from acquiring the mutex and executing before a higher-priority thread is awakened.

Several threads may use the same condition variables to wait on different predicates. The waiting threads must verify that the predicate is satisfied when they return from the wait. The threads that modify x or y do not need to know what conditions are being waited for; they just need to know which condition variable is being used.

Exercise 13.15

Compare the use of condition variables with the use of sigsuspend as described in Example 8.24 on page 275.

Answer:

The concepts are similar. Example 8.24 blocks the signal and tests the condition. Blocking the signal is analogous to locking the mutex since the signal handler cannot access the global variable sigreceived while the signal is blocked. The sigsuspend atomically unblocks the signal and suspends the process. When sigsuspend returns, the signal is blocked again. With condition variables, the thread locks the mutex to protect its critical section and tests the condition. The pthread_cond_wait atomically releases the mutex and suspends the process. When pthread_cond_wait returns, the thread owns the mutex again.

13.4.1 Creating and destroying condition variables

POSIX represents condition variables by variables of type pthread_cond_t . A program must always initialize pthread_cond_t variables before using them. For statically allocated pthread_cond_t variables with the default attributes, simply assign PTHREAD_COND_INITIALIZER to the variable. For variables that are dynamically allocated or don't have the default attributes, call pthread_cond_init to perform initialization. Pass NULL for the attr parameter of pthread_cond_init to initialize a condition variable with the default attributes. Otherwise, first create and initialize a condition variable attribute object in a manner similar to that used for thread attribute objects.

  SYNOPSIS  #include <pthread.h>   int pthread_cond_init(pthread_cond_t *restrict cond,                         const pthread_condattr_t *restrict attr);   pthread_cont_t cond = PTHREAD_COND_INITIALIZER;  POSIX:THR  

If successful, pthread_cond_init returns 0. If unsuccessful , pthread_cond_init returns a nonzero error code. The following table lists the mandatory errors for pthread_cond_init .

error

cause

EAGAIN

system lacked nonmemory resources needed to initialize *cond

ENOMEM

system lacked memory resources needed to initialize *cond

Example 13.16

The following code segment initializes a condition variable.

 pthread_cond_t barrier; int error; if (error = pthread_cond_init(&barrier, NULL));    fprintf(stderr, "Failed to initialize barrier:%s\n", strerror(error)); 

The code assumes that strerror will not be called by multiple threads. Otherwise, strerror_r of Section 13.7 should be used.

Exercise 13.17

What happens if a thread tries to initialize a condition variable that has already been initialized ?

Answer:

The POSIX standard explicitly states that the results are not defined, so you should avoid doing this.

The pthread_cond_destroy function destroys the condition variable referenced by its cond parameter. A pthread_cond_t variable that has been destroyed with pthread_cond_destroy can be reinitialized with pthread_cond_init .

  SYNOPSIS  #include <pthread.h>   int pthread_cond_destroy(pthread_cond_t *cond);  POSIX:THR  

If successful, pthread_cond_destroy returns 0. If unsuccessful, it returns a nonzero error code. No mandatory errors are defined for pthread_cond_destroy .

Example 13.18

The following code segment destroys the condition variable tcond .

 pthread_cond_t tcond; if (error = pthread_cond_destroy(&tcond))    fprintf(stderr, "Failed to destroy tcond:%s\n", strerror(error)); 
Exercise 13.19

What happens if a thread references a condition variable that has been destroyed?

Answer:

POSIX explicitly states that the results are not defined. The standard also does not define what happens when a thread attempts to destroy a condition variable on which other threads are blocked.

13.4.2 Waiting and signaling on condition variables

Condition variables derive their name from the fact that they are called in conjunction with testing a predicate or condition. Typically, a thread tests a predicate and calls pthread_cond_wait if the test fails. The pthread_cond_timedwait function can be used to wait for a limited time. The first parameter of these functions is cond , a pointer to the condition variable. The second parameter is mutex , a pointer to a mutex that the thread acquired before the call. The wait operation causes the thread to release this mutex when the thread is placed on the condition variable wait queue. The pthread_cond_timedwait function has a third parameter, a pointer to the time to return if a condition variable signal does not occur first. Notice that this value represents an absolute time, not a time interval.

  SYNOPSIS  #include <pthread.h>   int pthread_cond_timedwait(pthread_cond_t *restrict cond,                         pthread_mutex_t *restrict mutex,                         const struct timespec *restrict abstime);   int pthread_cond_wait(pthread_cond_t *restrict cond,                         pthread_mutex_t *restrict mutex);  POSIX:THR  

If successful, pthread_cond_timedwait and pthread_cond_wait return 0. If unsuccessful, these functions return nonzero error code. The pthread_cond_timedwait function returns ETIMEDOUT if the time specified by abstime has expired . If a signal is delivered while a thread is waiting for a condition variable, these functions may resume waiting upon return from the signal handler, or they may return 0 because of a spurious wakeup .

Example 13.20

The following code segment causes a thread to (nonbusy) wait until a is greater than or equal to b .

 pthread_mutex_lock(&mutex); while (a < b)     pthread_cond_wait(&cond, &mutex); pthread_mutex_unlock(&mutex); 

The code omits error checking for clarity.

The calling thread should obtain a mutex before it tests the predicate or calls pthread_cond_wait . The implementation guarantees that pthread_cond_wait causes the thread to atomically release the mutex and block.

Exercise 13.21

What happens if one thread executes the code of Example 13.20 by using mutex and another thread executes Example 13.20 by using mutexA ?

Answer:

This is allowed as long as the two threads are not concurrent. The condition variable wait operations pthread_cond_wait and pthread_cond_timedwait effectively bind the condition variable to the specified mutex and release the binding on return. POSIX does not define what happens if threads use different mutex locks for concurrent wait operations on the same condition variable. The safest way to avoid this situation is to always use the same mutex with a given condition variable.

When another thread changes variables that might make the predicate true, it should awaken one or more threads that are waiting for the predicate to become true. The pthread_cond_signal function unblocks at least one of the threads that are blocked on the condition variable pointed to by cond . The pthread_cond_broadcast function unblocks all threads blocked on the condition variable pointed to by cond .

  SYNOPSIS  #include <pthread.h>   int pthread_cond_broadcast(pthread_cond_t *cond);   int pthread_cond_signal(pthread_cond_t *cond);  POSIX:THR  

If successful, pthread_condition_broadcast and pthread_condition_signal return 0. If unsuccessful, these functions return a nonzero error code.

Example 13.22

Suppose v is a condition variable and m is a mutex. The following is a proper use of the condition variable to access a resource if the predicate defined by test_condition() is true. This code omits error checking for clarity.

 static pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t v = PTHREAD_COND_INITIALIZER; pthread_mutex_lock(&m); while (!test_condition())                      /* get resource */    pthread_cond_wait(&v, &m);     /* do critical section, possibly changing test_condition() */ pthread_cond_signal(&v);          /* inform another thread */ pthread_mutex_unlock(&m);                                       /* do other stuff */ 

When a thread executes the pthread_cond_wait in Example 13.22, it is holding the mutex m . It blocks atomically and releases the mutex, permitting another thread to acquire the mutex and modify the variables in the predicate. When a thread returns successfully from a pthread_cond_wait , it has acquired the mutex and can retest the predicate without explicitly reacquiring the mutex. Even if the program signals on a particular condition variable only when a certain predicate is true, waiting threads must still retest the predicate. The POSIX standard specifically allows pthread_cond_wait to return, even if no thread has called pthread_cond_signal or pthread_cond_broadcast .

Program 6.2 on page 187 implements a simple barrier by using a pipe. Program 13.13 implements a thread-safe barrier by using condition variables. The limit variable specifies how many threads must arrive at the barrier (execute the waitbarrier ) before the threads are released from the barrier. The count variable specifies how many threads are currently waiting at the barrier. Both variables are declared with the static attribute to force access through initbarrier and waitbarrier . If successful, the initbarrier and waitbarrier functions return 0. If unsuccessful, these functions return a nonzero error code.

Remember that condition variables are not linked to particular predicates and that pthread_cond_wait can return because of spurious wakeups . Here are some rules for using condition variables.

  1. Acquire the mutex before testing the predicate.

  2. Retest the predicate after returning from a pthread_cond_wait , since the return might have been caused by some unrelated event or by a pthread_cond_signal that did not cause the predicate to become true.

  3. Acquire the mutex before changing any of the variables appearing in the predicate.

  4. Hold the mutex only for a short period of timeusually while testing the predicate or modifying shared variables.

  5. Release the mutex either explicitly (with pthread_mutex_unlock ) or implicitly (with pthread_cond_wait ).

Program 13.13 tbarrier.c

Implementation of a thread-safe barrier .

 #include <errno.h> #include <pthread.h> static pthread_cond_t bcond = PTHREAD_COND_INITIALIZER; static pthread_mutex_t bmutex = PTHREAD_MUTEX_INITIALIZER; static int count = 0; static int limit = 0; int initbarrier(int n) {              /* initialize the barrier to be size n */    int error;    if (error = pthread_mutex_lock(&bmutex))        /* couldn't lock, give up */       return error;    if (limit != 0) {                 /* barrier can only be initialized once */       pthread_mutex_unlock(&bmutex);       return EINVAL;    }    limit = n;    return pthread_mutex_unlock(&bmutex); } int waitbarrier(void) {    /* wait at the barrier until all n threads arrive */    int berror = 0;    int error;    if (error = pthread_mutex_lock(&bmutex))        /* couldn't lock, give up */       return error;    if (limit <=  0) {                       /* make sure barrier initialized */       pthread_mutex_unlock(&bmutex);       return EINVAL;    }    count++;    while ((count < limit) && !berror)       berror =  pthread_cond_wait(&bcond, &bmutex);    if (!berror)       berror = pthread_cond_broadcast(&bcond);           /* wake up everyone */    error = pthread_mutex_unlock(&bmutex);    if (berror)       return berror;    return error; } 
Team-FLY


Unix Systems Programming
UNIX Systems Programming: Communication, Concurrency and Threads
ISBN: 0130424110
EAN: 2147483647
Year: 2003
Pages: 274

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net