Now that we have added condition variables to our arsenal of threaded programming tools, we will solve that problem. The new version, creatively named alarm_cond.c uses a timed condition wait rather than sleep to wait for an alarm expiration time. When main inserts a new entry at the head of the list, it signals the condition variable to awaken alarm_thread immediately. The alarm_thread then requeues the alarm on which it was waiting, to sort it properly with respect to the new entry, and tries again.
20,22 Part 1 shows the declarations for alarm_cond.c. There are two additions to this section, compared to alarm_mutex.c: a condition variable called alarm_cond and the current_alarm variable, which allows main to determine the expiration time of the alarm on which alarm_thread is currently waiting. The current_alarm variable is an optimization—main does not need to awaken alarm_thread unless it is either idle, or waiting for an alarm later than the one main has just inserted.
■ alarm_cond.c part 1 declarations
1 #include
2 #include
3 #include "errors.h"
4
5 /*
6 * The "alarm" structure now contains the time_t (time since the
7 * Epoch, in seconds) for each alarm, so that they can be
8 * sorted. Storing the requested number of seconds would not be
9 * enough, since the "alarm thread" cannot tell how long it has
10 * been on the list.
11 */
12 typedef struct alarm_tag {
13 struct alarm_tag *link;
14 int seconds;
15 time_t time; /* seconds from EPOCH */
16 char message[64];
17 } alarm_t;
18
19 pthread_mutex_t alarm_mutex = PTHREAD_MUTEX_INITIALIZER;
20 pthread_cond_t alarm_cond = PTHREAD_COND_INITIALIZER;
21 alarm_t *alarm_list = NULL;
22 time_t current_alarm = 0;
Part 2 shows the new function alarm_insert. This function is nearly the same as the list insertion code from alarm_mutex.c, except that it signals the condition variable alarm_cond when necessary. I made alarm_insert a separate function because now it needs to be called from two places—once by main to insert a new alarm, and now also by alarm_thread to reinsert an alarm that has been "preempted" by a new earlier alarm.
9-14 I have recommended that mutex locking protocols be documented, and here is an example: The alarm_insert function points out explicitly that it must be called with the alarm_mutex locked.
48-53 If current_alarm (the time of the next alarm expiration) is 0. then the alarm_ thread is not aware of any outstanding alarm requests, and is waiting for new work. If current_alarm has a time greater than the expiration time of the new alarm, then alarm_thread is not planning to look for new work soon enough to handle the new alarm. In either case, signal the alarm_cond condition variable so that alarm_thread will wake up and process the new alarm.
■ alarm_cond.c part 2 alarm_insert
1 /*
2 * Insert alarm entry on list, in order.
3 */
4 void alarm_insert (alarm_t *alarm)
5 {
6 int status;
7 alarm_t **last, *next;
8
9 /*
10 * LOCKING PROTOCOL:
11 *
12 * This routine requires that the caller have locked the
13 * alarm_mutex!
14 */
15 last = &alarm_list;
16 next = *last;
17 while (next != NULL) {
18 if (next->time >= alarm->time) {
19 alarm->link = next;
20 *last = alarm;
21 break;
22 }
23 last = &next->link;
24 next = next->link;
25 }
26 /*
27 * If we reached the end of the list, insert the new alarm
28 * there. ("next" is NULL, and "last" points to the link
29 * field of the last item, or to the list header.)
30 */
31 if (next == NULL) {
32 *last = alarm;
33 alarm->link = NULL;
34 }
35 #ifdef DEBUG
36 printf ("[list: ");