Add Book to My BookshelfPurchase This Book Online

Chapter 3 - Synchronizing Pthreads

Pthreads Programming
Bradford Nichols, Dick Buttlar and Jacqueline Proulx Farrell
 Copyright © 1996 O'Reilly & Associates, Inc.

Condition Variables
While a mutex lets threads synchronize by controlling their access to data, a condition variable lets threads synchronize on the value of data. Cooperating threads wait until data reaches a particular state or until a certain event occurs. Condition variables provide a kind of notification system among threads. As mentioned earlier, if Pthreads didn't offer condition variables, but only provided mutexes, threads would need to poll the variable to determine when it reached a certain state.
Example 3-7 shows a simple use of a condition variable. We'll make the global variable count a shared resource that two threads increment and create the mutex count_mutex (in global scope) to protect it. We'll use the count_threshold_cv condition variable to represent an event—the count variable's reaching a defined threshold value, WATCH_COUNT.
The main routine creates two threads. Each of these threads runs the inc_count routine. The inc_count routine locks count_mutex, increments count, reads count in a printf statement, and tests for the threshold value. If count has reached its threshold value, inc_count calls pthread_cond_signal to notify the thread that's waiting for this particular event. Before exiting, inc_count releases the mutex. We'll create a third thread to run the watch_count task. The watch_count routine waits for inc_count to signal our count_threshold_cv condition variable.
Example 3-7: A Simple Condition Variable Example (cvsimple.c)
#include <stdio.h>
#include <pthread.h>
#define TCOUNT 10
#define WATCH_COUNT 12
int count = 0;
pthread_mutex_t count_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t count_threshold_cv = PTHREAD_COND_INITIALIZER;
int  thread_ids[3] = {0,1,2};
extern int
main(void)
{
     int      i;
     pthread_t threads[3];
     pthread_create(&threads[0],NULL,inc_count, &thread_ids[0]);
     pthread_create(&threads[1],NULL,inc_count, &thread_ids[1]);
     pthread_create(&threads[2],NULL,watch_count, &thread_ids[2]);
     for (i = 0; i < 3; i++) {
              pthread_join(threads[i], NULL);
     }
     return 0;
}
void watch_count(int *idp)
{
     pthread_mutex_lock(&count_mutex)
     while (count <= WATCH_COUNT) {
              pthread_cond_wait(&count_threshold_cv,
                               &count_mutex);
              printf("watch_count(): Thread %d,Count is %d\n",
                   *idp, count);
     }
     pthread_mutex_unlock(&count_mutex);
}
void inc_count(int *idp)
{
     for (i =0; i < TCOUNT; i++) {
              pthread_mutex_lock(&count_mutex);
              count++;
              printf("inc_count(): Thread %d, old count %d,\
                   new count %d\n", *idp, count - 1, count );
              if (count == WATCH_COUNT)
                   pthread_cond_signal(&count_threshold_cv);
              pthread_mutex_unlock(&count_mutex);
     }
}
A condition variable has a data type of pthread_cond_t. You can initialize it statically as we do in Example 3-7, or you can initialize it dynamically by calling pthread_cond_init, as follows:
pthread_cond_init(&count_threshold_cv, NULL);
After you initialize a condition variable, a thread can use it in one of two ways:
 The thread can wait on the condition variable.
To wait on a condition variable, a thread calls pthread_cond_wait or pthread_cond_timedwait. Both of these functions suspend the caller until another thread signals* on the condition variable. In addition, the pthread_cond_timedwait call lets you specify a timeout argument. If the condition is not signaled in the specified time, the thread is released from its wait.
Note that we do not use the term signals in the sense used in discussions of
UNIX signaling mechanisms. See the section called "
Condition Variables
and UNIX Signals
" later in this chapter.
 It can signal other threads waiting on the condition variable.
To release threads that are waiting on a condition variable, a thread calls pthread_cond_signal or pthread_cond_broadcast. The pthread_cond_signal function wakes up only one of the potentially many threads waiting on the condition; the pthread_cond_broadcast function awakens all of them.
In Example 3-7, the only thread waiting is the one running the watch_count task. The threads that signal are running the inc_count task.* The thread running watch_count first locks the count mutex before checking the count. This is important because, condition or no condition, that counter is still a shared piece of data. We don't want it to change while we're in the middle of checking it. If count is not the desired value, the thread calls pthread_cond_wait to put itself into a wait on the count_threshold_cv condition variable. The pthread_cond_wait function releases the count mutex while the thread is waiting so other threads have the opportunity to modify count. When the condition occurs and it is awakened, the thread running watch_count prints a message, unlocks the mutex, and exits. The threads running inc_count check the value of the count after each increment. If count reaches WATCH_COUNT, they use pthread_cond_signal to awaken the waiter.
 *Although the pthread_cond_broadcast function wakes all threads waiting on the condition, they all immediately compete for the associated mutex. Only one of them will succeed in locking the mutex and be able to continue in the code after thepthread_cond_wait call. See the discussion in the section called "When Many Threads Are Waiting" coming up.
Using a Mutex with a Condition Variable
It is important to use condition variables and mutexes together properly.
A call to pthread_cond_wait requires that a locked mutex be passed in along with the condition variable. The system releases the mutex on the caller's behalf when the wait for the condition begins. In concert with the actions of the waiting thread, the thread that issues the pthread_cond_signal or pthread_cond_broadcast call holds the mutex at the time of the call but must release it after the call. Then, when the system wakes it up, a waiting thread can regain control of the mutex. It too must release the mutex when it's finished with it.
It all sounds complicated, but what if the mutex and the condition variable weren't linked? If the condition were signaled without a mutex, the signaling thread might signal the condition before the waiting thread begins waiting for it—in which case the waiting thread would never wake up. If the system did not release the lock when the waiting thread entered the wait, no other thread could get the mutex and change the value of count such that the condition is met. The condition would never be signaled, and the program would deadlock. If the waiting thread didn't release the mutex, no other thread could get the mutex. Here, too, we'd wind up in a deadlock.
When Many Threads Are Waiting
If multiple threads are waiting on a condition variable, who gets awakened first when another thread issues a pthread_cond_signal call? As with threads waiting in a lock call to a mutex variable, the waiting threads are released according to their scheduling priority. If all waiting threads are of the same priority, they are released in a first-in first-out order for each pthread_cond_signal call that's issued.
The pthread_cond_broadcast function releases all threads at once from their waits on the condition variable, but there is a hitch. The system can select only one to which to give possession of the mutex. It does so by applying the same criterion it uses when selecting the thread it wakes when a phread_cond_signal call signals a condition—scheduling order. The chosen thread is given the mutex lock and continues in the code following its pthread_cond_wait call. The other threads are moved to the queue of threads that are waiting to acquire the mutex. Each will resume as each previous thread in the queue acquires the mutex and then releases it.
Checking the Condition on Wake Up: Spurious Wake Ups
This brings us to another aspect of using condition variables. After it's just been awakened, the waiting thread in our example reenters its while loop to check the value of count one more time. Is that really necessary, or are we guilty of sloppy coding? After all, count must have reached its threshold limit, mustn't it, if the thread is now awake and that was the event on which it was sleeping?
Well, we check the event one more time primarily to ensure correctness: if multiple threads were waiting on the same condition variable, another thread could have already been awakened, perhaps decrementing the count, before our thread was able to run. Second, we want to guard against a condition known as a spurious wake up. Perhaps a signaling thread has, in error or due to an unexpected condition, awakened our waiting thread when the expected condition has not in fact been met. In addition, the Pthreads library allows an underlying threads library to issue spurious wake ups to a waiting thread without violating the standard. We need to guard against this possibility as well.
Condition Variable Attributes
When you initialize a condition variable dynamically (that is, by calling the pthread_cond_init function), the Pthreads library creates a condition variable attribute object for it. A Pthreads condition variable attribute object is of data type pthread_condattr_t.You initialize and deinitialize the condition variable attribute object by calling pthread_condattr_init and pthread_condattr_destroy, respectively.
A condition variable attribute object has a single, optional attribute that determines whether or not it can be seen by threads in other processes: process-shared. Using the process-shared attribute for condition variables is similar to using it for mutexes and involves many of the same issues. If you can set the process-shared attribute on your platform, _POSIX_THREAD_PROCESS_SHARED, the compile-time constant, will be TRUE. To set the process-shared attribute, supply either the PTHREAD_PROCESS_SHARED or the PTHREAD_PROCESS_PRIVATE constant in a call to pthread_condattr_setshared. To test the attribute's value, issue a call to pthread_condattr_getshared.
If you want the default attributes for a condition variable, pass the function pthread_condattr_init an attribute argument of NULL.
Condition Variables and UNIX Signals
The Pthreads standard does not define what should happen when a condition variable is signaled from within a <acronym>UNIX</acronym> signal handler. We'll provide some detail in Chapter 5, but, for now, let's make this clear: unlike UNIX signals, condition variables are synchronous. You wait on a condition variable, and you start up again when another thread signals you. The signal is not delivered by the system itself, and it is not delivered asynchronously. If you want asynchronous signals, you can certainly use them. In Chapter 5, we'll show how.
Condition Variables and Cancellation
There are also issues with cancellation and waiting on a condition variable. See the section called "The Complication with Cancellation" in Chapter 4.

Previous SectionNext Section
Books24x7.com, Inc © 2000 –  Feedback