<span style="font-size:18px;">#include<pthread.h>
#include<time.h>
#include "errors.h"
typedef struct alarm_tag{<span style="white-space:pre"> </span>//单个闹钟结构体,链表形式存储
struct alarm_tag *link;<span style="white-space:pre"> </span>//链表的下一个节点
int seconds;<span style="white-space:pre"> </span>//闹钟响铃所需经过的秒数(从创建时刻为起点)
time_t time;<span style="white-space:pre"> </span>//闹钟响铃的UNIX时间(从<span style="color: rgb(51, 51, 51); font-family: arial, 宋体, sans-serif; line-height: 24px; text-indent: 28px;">1970年1月1日0时0分0秒起的总秒数</span>)
char message[64];<span style="white-space:pre"> </span>//闹钟响铃时所打印的消息
}alarm_t;
pthread_mutex_t alarm_mutex = PTHREAD_MUTEX_INITIALIZER;<span style="white-space:pre"> </span>//定义互斥锁,使用缺省的初始值
pthread_cond_t alarm_cond = PTHREAD_COND_INITIALIZER;<span style="white-space:pre"> </span>//定义条件变量,使用缺省的初始值
alarm_t *alarm_list = NULL;<span style="white-space:pre"> </span>//闹钟链表的头结点
time_t current_alarm = 0;<span style="white-space:pre"> </span>//子线程当前处理的闹钟的响铃时间(UNIX时间),若为0则表示闹钟链表为空,子线程在阻塞等待主线程添加新的闹钟或表示子线程正在处理当前闹钟(一定要搞清楚这是两种状态)
void alarm_insert(alarm_t *alarm){<span style="white-space:pre"> </span>//闹钟链表插入新的闹钟,并且按照时间升序插入
int status;<span style="white-space:pre"> </span>//线程函数返回状态0为调用成功,其他值表示调用失败
alarm_t **last,*next;<span style="white-space:pre"> </span>//用于查找插入位置的两个指针,(这里查找插入链表的操作还是很有意思的,用指针的指针和一个指针进行查找插入)
last = &alarm_list;<span style="white-space:pre"> </span>//下面是查找插入的过程,不多赘述
next = *last;
while(next != NULL){
if(next->time >= alarm->time){
alarm->link = next;
*last = alarm;
break;
}
last = &next->link;
next = next->link;
}
if(next == NULL){
*last = alarm;
alarm->link = NULL;
}<span style="white-space:pre"> </span>//查找插入结束,新闹钟已经插入正确位置
#ifdef DEBUG
printf("[list: ");
for(next = alarm_list; next != NULL; next = next->link)
printf("%d(%d)[\"%s\"] ",next->time,
next->time - time(NULL), next->message);
printf("]\n");
#endif<span style="white-space:pre"> </span>//*****下面这一段比较重要*****//
if(current_alarm == 0 || alarm->time < current_alarm){<span style="white-space:pre"> </span>//检测当前子线程的状态,如果1>当前子线程在等待新的闹钟加入
current_alarm = alarm->time;<span style="white-space:pre"> </span>//<span style="font-family: Arial, Helvetica, sans-serif;">或2>者正在处理一个闹钟并还未到达等待闹钟响起的时间点</span>
status = pthread_cond_signal(&alarm_cond);<span style="white-space:pre"> </span>//<span style="font-family: Arial, Helvetica, sans-serif;">或3>当前等待响起的闹钟的响起时间比新加入的闹钟晚</span>
if(status != 0)<span style="white-space:pre"> </span>//则更改current_alarm的状态并通过条件变量发射信号通知子线程
err_abort(status,"Signal cond");
}
}
void *alarm_thread(void *arg){<span style="white-space:pre"> </span>//子线程的处理函数
alarm_t *alarm;<span style="white-space:pre"> </span>//用于指向当前欲处理的闹钟节点
struct timespec cond_time;<span style="white-space:pre"> </span>//存放条件变量阻塞时间的对象
time_t now;<span style="white-space:pre"> </span>//当前时间
int status, expired;<span style="white-space:pre"> </span>//status也是表示线程函数返回值的变量 , expired用于表示当前闹钟处理结果的flag, 如果为1则闹钟响铃,打印消息, 为0则暂时不对当前闹钟做响铃操作
status = pthread_mutex_lock(&alarm_mutex);<span style="white-space:pre"> </span>//锁定互斥量. 此时主线程将被阻塞在等待输入新闹钟或者将新输入的闹钟信息添加到链表之前的位置
if(status != 0)
err_abort(status,"Lock mutex");
while(true){<span style="white-space:pre"> </span>//循环处理当前状态, 1.闹钟链表为空.等待主线程插入新闹钟 2. 等待当前闹钟响铃,同时检测条件变量是否发生改变
current_alarm = 0;<span style="white-space:pre"> </span>//子线程准备开始处理当前闹钟,但还未开始等待闹钟响铃,可以等待alarm_insert发信号添加更早响铃的闹钟,所以current_alarm设置为0
while(alarm_list == NULL){<span style="white-space:pre"> </span>//循环检测谓词(如果链表为空,则等待主线程加入新的闹钟),当条件变量接受信号后也要检测一次,以保证条件状态没有在解锁条件变量,锁定互斥量的两个操作之间被改变
status = pthread_cond_wait(<span style="white-space:pre"> </span>//锁定条件变量,等待信号,并解锁互斥量,</span>
<span style="font-size:18px;"><span style="font-family: Arial, Helvetica, sans-serif;"> &alarm_cond, &alarm_mutex);<span style="white-space:pre"> </span>//</span><span style="font-family: Arial, Helvetica, sans-serif;">使主线程继续执行(或等待用户输入闹钟信息,或处理已输入但未加入链表的闹钟信息)</span></span>
<span style="font-size:18px;"> if(status != 0)
err_abort(status,"Wait on cond");
}
alarm = alarm_list;<span style="white-space:pre"> </span>//取链表第一个节点作为当前处理的闹钟(因为头结点的闹钟最先响起)
alarm_list = alarm->link;<span style="white-space:pre"> </span>//头结点后移
now = time(NULL);<span style="white-space:pre"> </span>//当前时间
expired = 0;<span style="white-space:pre"> </span>//当前闹钟处理状态初值设为0
if(alarm->time > now){<span style="white-space:pre"> </span>//如果闹钟响铃时间大于当前时间,则进入等待响铃并同时检测是否有响铃更早的新闹钟加入的状态
#ifdef DEBUG
printf("[waiting: %d(%d)\"%s\"]\n",alarm->time,
alarm->time - time(NULL), alarm->message);
#endif
cond_time.tv_sec = alarm->time;<span style="white-space:pre"> </span>//等待终止时间设置为响铃时间
cond_time.tv_nsec = 0;<span style="white-space:pre"> </span>//闹钟不需要精确到纳秒,所以设为0
current_alarm = alarm->time;<span style="white-space:pre"> </span>//设置全局变量的当前闹钟响铃时间
while(current_alarm == alarm->time){<span style="white-space:pre"> </span>//循环检测谓词,如果加入了更早响铃的闹钟,current_alarm将不等于当前处理的闹钟响铃时间,否则一直等到当前处理的闹钟到达响铃时间,两种情况都将停止阻塞,分别处理
status = pthread_cond_timedwait(&alarm_cond, &alarm_mutex,&cond_time);<span style="white-space:pre"> </span>//锁定条件变量等待信号或等待到达响铃时间
if(status == ETIMEDOUT){<span style="white-space:pre"> </span>//如果条件变量返回的是阻塞超时,则说明期间没有更早的闹钟加入,
expired = 1;<span style="white-space:pre"> </span>//因此当前闹钟到达响铃时间,当前处理状态置为1,表示闹钟响铃
break;<span style="white-space:pre"> </span>//跳出谓词检测的循环(因为此时谓词一定还是成立的)
}
if(status != 0)
err_abort(status,"Cond timedwait");
}
if(!expired)<span style="white-space:pre"> </span>//如果条件变量是接收了信号,使得谓词不成立,即current_alarm != alarm->time则说明加入了更早响铃的闹钟
alarm_insert(alarm);<span style="white-space:pre"> </span>//此时应该把当前处理的闹钟信息重新加回闹钟链表等待处理
}else<span style="white-space:pre"> </span>//否则(即如果当前处理的闹钟响铃时间比当前时间还早,则也立即将处理状态置为1,即打印响铃信息)
expired = 1;
if(expired){<span style="white-space:pre"> </span>//检测当前闹钟处理状态为响铃
printf("(%d) %s\n",alarm->seconds,alarm->message);<span style="white-space:pre"> </span>//响铃,打印响铃消息
free(alarm);<span style="white-space:pre"> </span>//释放当前处理的闹钟节点
}
}
}
int main(int argc,char *argv[]){
int status;<span style="white-space:pre"> </span>//同上的status
char line[128];<span style="white-space:pre"> </span>//输入缓冲区
alarm_t *alarm;<span style="white-space:pre"> </span>//闹钟节点指针
pthread_t thread;<span style="white-space:pre"> </span>//线程
status = pthread_create(&thread,NULL,alarm_thread,NULL);<span style="white-space:pre"> </span>//创建闹钟处理线程
if(status != 0)
err_abort(status,"Create alarm thread");
while(true){<span style="white-space:pre"> </span>//循环处理输入信息
printf("Alarm>");<span style="white-space:pre"> </span>//提示符
if(fgets(line,sizeof(line),stdin) == NULL)exit(0);//一些输入上的处理
if(strlen(line) <= 1)continue;
alarm = (alarm_t*)malloc(sizeof(alarm_t));<span style="white-space:pre"> </span>//分配新的闹钟节点内存空间
if(alarm == NULL)
errno_abort("Allocate alarm");
if(sscanf(line,"%d %64[^\n]",&alarm->seconds,alarm->message) < 2){//将输入信息写入新分配的节点中,
fprintf(stderr, "Bad command\n");<span style="white-space:pre"> </span>//如果输入格式错误打印消息提示
free(alarm);<span style="white-space:pre"> </span>//并释放当前节点
}else{<span style="white-space:pre"> </span>//否则锁定互斥量并将新加入的闹钟节点加入链表
status = pthread_mutex_lock(&alarm_mutex);
if(status != 0)
err_abort(status,"Lock mutex");
alarm->time = time(NULL) + alarm->seconds;
alarm_insert(alarm);
status = pthread_mutex_unlock(&alarm_mutex);<span style="white-space:pre"> </span>//解锁互斥量继续等待用户输入
if(status != 0)
err_abort(status, "Unlock mutex");
}
}
}</span>
整个程序比较重要的点是alarm_insert中检测current_alarm和新插入的alarm并判断是否给闹钟处理线程发送信号.
这个例程是 POSIX多线程程序设计 一书中将条件变量的例程, 最开始看作者讲条件变量,自己并不能理解的很清晰,
不过把这个例程搬到电脑上跑一下,然后一行一行的去理解,整个程序的逻辑脉络清晰之后还是很好理解条件变量这个东西的
而且这个例程真的挺漂亮的,很值得学习.
最后提醒一下 编译不要忘了 -lpthread
$ gcc alarm_cond.c -o alarm_cond -lpthread
16.3.25.01:14