Êíèãà: Programming with POSIX® Threads

3.3.2 Waiting on a condition variable

3.3.2 Waiting on a condition variable

int pthread_cond_wait (pthread_cond_t *cond,

pthread_mutex_t *mutex);

int pthread_cond_timedwait (pthread_cond_t *cond,

pthread_mutex_t *mutex,

struct timespec *expiration);

Each condition variable must be associated with a specific mutex, and with a predicate condition. When a thread waits on a condition variable it must always have the associated mutex locked. Remember that the condition variable wait operation will unlock the mutex for you before blocking the thread, and it will relock the mutex before returning to your code.

All threads that wait on any one condition variable concurrently (at the same time) must specify the same associated mutex. Pthreads does not allow thread 1, for example, to wait on condition variable A specifying mutex A while thread 2 waits on condition variable A specifying mutex B. It is, however, perfectly reasonable for thread 1 to wait on condition variable A specifying mutex A while thread 2 waits on condition variable B specifying mutex A. That is, each condition variable must be associated, at any given time, with only one mutex—but a mutex may have any number of condition variables associated with it.

It is important that you test the predicate after locking the appropriate mutex and before waiting on the condition variable. If a thread signals or broadcasts a condition variable while no threads are waiting, nothing happens. If some other thread calls pthread_cond_wait right after that, it will keep waiting regardless of the fact that the condition variable was just signaled, which means that if a thread waits when it doesn't have to, it may never wake up. Because the mutex remains locked until the thread is blocked on the condition variable, the predicate cannot become set between the predicate test and the wait—the mutex is locked and no other thread can change the shared data, including the predicate.

Always test your predicate; and then test it again!

It is equally important that you test the predicate again when the thread wakes up. You should always wait for a condition variable in a loop, to protect against program errors, multiprocessor races, and spurious wakeups. The following short program, cond.c, shows how to wait on a condition variable. Proper predicate loops are also shown in all of the examples in this book that use condition variables, for example, alarm_cond.c in Section 3.3.4.

20-37 The wait_thread sleeps for a short time to allow the main thread to reach its condition wait before waking it, sets the shared predicate (data.value), and then signals the condition variable. The amount of time for which wait_thread will sleep is controlled by the hibernation variable, which defaults to one second.

51-52 If the program was run with an argument, interpret the argument as an integer value, which is stored in hibernation. This controls the amount of time for which wait.thread will sleep before signaling the condition variable.

68-83 The main thread calls pthread_cond_timedwait to wait for up to two seconds (from the current time). If hibernation has been set to a value of greater than two seconds, the condition wait will time out, returning ETIMEDOUT. If hibernation has been set to two, the main thread and wait_thread race, and, in principle, the result could differ each time you run the program. If hibernation is set to a value less than two, the condition wait should not time out.

? cond.c

1 #include <pthread.h>
2 #include <time.h>
3 #include "errors.h" 4
5 typedef struct my_struct_tag {
6  pthread_mutex_t mutex;
7  pthread_cond_t cond;
8  int value;
9 } my_struct_t;

10
11 my_struct_t data = {
12  PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER, 0};
13
14 int hibernation = 1; /* Default to 1 second */
15
16 /*
17 * Thread start routine. It will set the main thread's predicate
18 * and signal the condition variable.
19 */
20 void *
21 wait_thread (void *arg)
22 {
23 int status;

24
25 sleep (hibernation);
26 status = pthread_mutex_lock (&data.mutex);
27 if (status != 0)
28  err_abort (status, "Lock mutex");
29 data.value = 1; /* Set predicate */
30 status = pthread_cond_signal (&data.cond);
31 if (status != 0)
32  err_abort (status, "Signal condition");
33 status = pthread_mutex_unlock (&data.mutex);
34 if (status != 0)
35  err_abort (status, "Unlock mutex");
36 return NULL;
37 }
/* Protects access to value */ /* Signals change to value */ /* Access protected by mutex */
38
39 int main (int argc, char *argv[])
40 {
41 int status;
42 pthread_t wait_thread_id;
43 struct timespec timeout;

44
45 /*
46 * If an argument is specified, interpret it as the number
47 * of seconds for wait_thread to sleep before signaling the
48 * condition variable. You can play with this to see the
49 * condition wait below time out or wake normally.
50 */
51 if (argc > 1)
52  hibernation = atoi (argv[l]);

53
54 /*
55 * Create wait_thread.
56 */
57 status = pthread_create (
58  &wait_thread_id, NULL, wait_thread, NULL);
59 if (status != 0)
60  err_abort (status, "Create wait thread");

61
62 /*
63 * Wait on the condition variable for 2 seconds, or until
64 * signaled by the wait_thread. Normally, wait_thread
65 * should signal. If you raise "hibernation" above 2
66 * seconds, it will time out.
67 */
68 timeout.tv_sec = time (NULL) + 2;
69 timeout.tv_nsec = 0;
70 status = pthread_mutex_lock (&data.mutex);
71 if (status != 0)
72 err_abort (status, "Lock mutex");
73
74 while (data.value == 0) { 
75  status = pthread_cond_timedwait (
76  &data.cond, &data.mutex, &timeout);
77 if (status == ETIMEDOUT) {
78 printf ("Condition wait timed out.n");
79 break;
80 }
81 else if (status != 0)
82 err_abort (status, "Wait on condition");
83 }

84
85 if (data.value != 0)
86  printf ("Condition was signaled.n");
87 status = pthread_mutex_unlock (&data.mutex);
88 if (status != 0)
89  err_abort (status, "Unlock mutex");
90 return 0;
91 }

There are a lot of reasons why it is a good idea to write code that does not assume the predicate is always true on wakeup, but here are a few of the main reasons:

Intercepted wakeups: Remember that threads are asynchronous. Waking up from a condition variable wait involves locking the associated mutex. But what if some other thread acquires the mutex first? It may, for example, be checking the predicate before waiting itself. It doesn't have to wait, since the predicate is now true. If the predicate is "work available," it will accept the work. When it unlocks the mutex there may be no more work. It would be expensive, and usually counterproductive, to ensure that the latest awakened thread got the work.

Loose predicates: For a lot of reasons it is often easy and convenient to use approximations of actual state. For example, "there may be work" instead of "there is work." It is often much easier to signal or broadcast based on "loose predicates" than on the real "tight predicates." If you always test the tight predicates before and after waiting on a condition variable, you're free to signal based on the loose approximations when that makes sense. And your code will be much more robust when a condition variable is signaled or broadcast accidentally. Use of loose predicates or accidental wakeups may turn out to be a performance issue; but in many cases it won't make a difference.

Spurious wakeups: This means that when you wait on a condition variable, the wait may (occasionally) return when no thread specifically broadcast or signaled that condition variable. Spurious wakeups may sound strange, but on some multiprocessor systems, making condition wakeup completely predictable might substantially slow all condition variable operations. The race conditions that cause spurious wakeups should be considered rare.

It usually takes only a few instructions to retest your predicate, and it is a good programming discipline. Continuing without retesting the predicate could lead to serious application errors that might be difficult to track down later. So don't make assumptions: Always wait for a condition variable in a while loop testing the predicate.

You can also use the pthread_cond_timedwait function, which causes the wait to end with an ETIMEDOUT status after a certain time is reached. The time is an absolute clock time, using the POSIX.1b struct timespec format. The timeout is absolute rather than an interval (or "delta time") so that once you've computed the timeout it remains valid regardless of spurious or intercepted

wakeups. Although it might seem easier to use an interval time, you'd have to recompute it every time the thread wakes up, before waiting again—which would require determining how long it had already waited.

When a timed condition wait returns with the ETIMEDOUT error, you should test your predicate before treating the return as an error. If the condition for which you were waiting is true, the fact that it may have taken too long usually isn't important. Remember that a thread always relocks the mutex before returning from a condition wait, even when the wait times out. Waiting for a locked mutex after timeout can cause the timed wait to appear to have taken a lot longer than the time you requested.

Îãëàâëåíèå êíèãè


Ãåíåðàöèÿ: 1.583. Çàïðîñîâ Ê ÁÄ/Cache: 3 / 0
ïîäåëèòüñÿ
Ââåðõ Âíèç