A lower-priority thread, for example, might lock the mutex while another thread was about to awaken a very high- priority thread, delaying scheduling of the high-priority thread. If the mutex remains locked while signaling, this cannot happen—the high-priority waiter will be placed before the lower-priority waiter on the mutex, and will be scheduled first.
3.3.4 One final alarm program
It is time for one final version of our simple alarm program. In alarm_ mutex.c, we reduced resource utilization by eliminating the use of a separate execution context (thread or process) for each alarm. Instead of separate execution contexts, we used a single thread that processed a list of alarms. There was one problem, however, with that approach—it was not responsive to new alarm commands. It had to finish waiting for one alarm before it could detect that another had been entered onto the list with an earlier expiration time, for example, if one entered the commands '10 message 1' followed by '5 message 2.'
*There is an optimization, which I've called 'wait morphing,' that moves a thread directly from the condition variable wait queue to the mutex wait queue in this case, without a context switch, when the mutex is locked. This optimization can produce a substantial performance benefit for many applications.
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 <pthread.h>
2 #include <time.h>
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: ');