1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
| ![交错获取筷子](/images/操作系统/9.png)
执行完毕的哲学家进程马上归还筷子资源, ![归还资源的哲学家线程](/images/操作系统/10.png)
这样就使线程间可以依次执行完毕。主线程最后统计成功的数目。如果哲学家线程发生了死锁,则统计数会是0。经过同步控制的哲学家线程具有相互协调资源的能力,而不发生死锁,统计数会是5。
这里对信号量的操作使用的是```sem_timedwait```函数,它在一个规定的时间尝试获取信号量,如果信号量值是可用的(大于0),则该函数马上返回0值,如果信号量值是不可用的(不大于0),则该方法会等待指定的时长,在阻塞指定时长后返回值是-1。```sem_timedwait```函数使用的是绝时间值。
```sem_getvalue(&sem, &semvalue)```;该方法直接查看信号量的值。```sem_post(&sem)```方法增加信号量的值,相当于归还资源;```gettimeofday```方法获得机器当前时间绝对值。```time_add_ms```对指定时间变量增加给定的时长。
本实训核心内容是要求学生理解哲学家线程死锁的发生,以及如何避免发生死锁,要求学生对并发线程有较深刻的理解,了解linux平台线程的运用和信号量的使用。
#### 测试说明
项目没有输入数据,输出数据是两种情况下线程成功运行的数目。
测试输入:无 预期输出:1,1(非真正结果)
------
开始你的任务吧,祝你成功!
```c++ //stu001.c 哲学家进餐问题,程序模板,由学生完成缺失代码 #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <semaphore.h>
static sem_t sem_chops[5];//筷子信号量 static sem_t sem_stop;//该信号量用于等待。 //时间递增方法 void time_add_ms(struct timeval *time, uint ms) { time->tv_usec += ms * 1000; // 微秒 = 毫秒 * 1000 if(time->tv_usec >= 1000000) // 进位,1000 000 微秒 = 1 秒 { time->tv_sec += time->tv_usec / 1000000; time->tv_usec %= 1000000; } } //100毫秒 #define TIMEOUT_FIRST 100 //200毫秒 #define TIMEOUT_HOLD 200 //200毫秒 #define TIMEOUT_PHIEND 200 //200毫秒 #define TIME_REPORT 200
struct timeval begintime; //获取的机器初始开始时间值 struct timespec first_time; //首次获取筷子信号量 struct timespec phi_holdtime; //保持一段时间 struct timespec report_time; //汇报线程完成情况时间 struct timespec phi_endtime; //哲学家线程结束绝对时间值 static int finish[5];
//哲学家线程没有进行有效同步控制 void* Philosopher(void *arg) { int iIndex=*((int*)arg); int iRetWait1=-1,iRetWait2=-1; //iRetWait1是获取筷子资源1的结果,成功返回0,失败返回-1
//iRetWait2是获取筷子资源2的结果,成功返回0,失败返回-1 //获取第一个筷子成功后还要保持一段时间,继续请求下一个新筷子 //begin ******哲学家线程获取筷子资源进餐
//筷子信号量 //首次获取筷子信号量 iRetWait1=sem_timedwait(sem_chops+iIndex,&first_time);//获取筷子时间结束 //该信号量用于等待 //保持一段时间 sem_timedwait(&sem_stop,&phi_holdtime); //筷子信号量 //首次获取筷子信号量 iRetWait2=sem_timedwait(sem_chops+(iIndex+1)%5,&first_time); //获取筷子时间结束 //end if((iRetWait1==0)&&(iRetWait2==0)){ //检查获得两个筷子资源结果,都成功则为就餐成功 //设置任务完成标志为1,线程结束 finish[iIndex]=1; //释放筷子资源iIndex sem_post(&sem_chops[iIndex]); //释放筷子资源 sem_post(&sem_chops[(iIndex+1)%5]); } //线程结束时间点:约为700MS return; }
//哲学家线程进行有效同步控制 void* PhilosopherGood(void *arg) { int iIndex=*((int*)arg); int iRetWait1=-1,iRetWait2=-1; int i=0; /*begin ******哲学家线程获取筷子资源进餐-受同步控制********************* //根据线程编号选择筷子编号并尝试获取第一个筷子,未成功获取则等待一段时间后重试 //成功后继续请求下一个新筷子 ****end*****************************************/ for(i=0;(i<5)&&(iRetWait1!=0);i++){ //获取第一根筷子 if(iIndex%2==1){ iRetWait1=sem_timedwait(sem_chops+iIndex,&first_time); } iRetWait2=sem_timedwait(sem_chops+(iIndex+1)%5,&first_time); //设置任务完成标志为1,线程结束 finish[iIndex]=1; //释放筷子资源iIndex sem_post(&sem_chops[iIndex]); //释放筷子资源 sem_post(&sem_chops[(iIndex+1)%5]); } //线程结束时间点:约为700MS return; }
//该线程用于检测哲学家未经同步控制完成任务的数量 int ReporterPhi(void*(*phiFunc)(void*)) { int iFinishCount=0; pthread_t pids[5]; int arg[5]; int iRetWait1=-1,iRetWait2=-1,retVal=-1; int i=0; gettimeofday(&begintime, NULL); //第一次获取筷子资源时间点:100MS time_add_ms(&begintime, TIMEOUT_FIRST); first_time.tv_sec = begintime.tv_sec; first_time.tv_nsec = begintime.tv_usec * 1000; //保持获取筷子资源到时间点:300MS time_add_ms(&begintime, TIMEOUT_HOLD); phi_holdtime.tv_sec = begintime.tv_sec; phi_holdtime.tv_nsec = begintime.tv_usec * 1000; //哲学家线程结束时间点:500毫秒 time_add_ms(&begintime, TIMEOUT_PHIEND); phi_endtime.tv_sec= begintime.tv_sec; phi_endtime.tv_nsec= begintime.tv_usec * 1000; //汇报线程完成情况时间点:700MS time_add_ms(&begintime, TIME_REPORT); report_time.tv_sec = begintime.tv_sec; report_time.tv_nsec = begintime.tv_usec * 1000;
sem_init(&sem_stop,0,0);//该信号量初值为0 //初始化完成数组为0,筷子信号量值为1 for(i=0;i<5;i++) { finish[i]=0; //设置信量初值为1 sem_init(sem_chops+i,0,1); } for(i=0;i<5;i++) { arg[i]=i;//传给线程的参数值,用来区分线程实体 //创建消费者线程,pids+i是线程ID的保存地址,arg+i是线程的参数指针 pthread_create(pids+i,NULL,phiFunc,(void *)(&arg[i])); } //等待哲学家线程结束,但都没有完成任务。 for(i=0;i<5;i++) { pthread_join(pids[i],NULL); //主线程等待生产者线程结束 } //等待到报告时间点:700MS retVal = sem_timedwait(&sem_stop, &report_time); iFinishCount=0; for(i=0;i<5;i++) { if(finish[i]==1) {iFinishCount++;} } return iFinishCount; }
int main(int argc,char * argv[]) { int i=0; int phi1=-1,phi2=-1; //调用无同步控制的哲学家线程 phi1=ReporterPhi(Philosopher); //调用有同步控制的哲学家线程 phi2=ReporterPhi(PhilosopherGood); //输出两种情况下完成任务的线程数 printf("%d,%d",phi1,phi2); //销毁信号量资源 for(i=0;i<5;i++) { sem_destroy(sem_chops+i); } sem_destroy(&sem_stop); return 0; }
|