
1、定义一个数据缓存buffer及用于实现同步互斥的信号量。
2、定义一个读者函数:
●当有写者在占用buffer时,读者应该等待,直到写者不再使用该buffer。
●当有其他读者在占用buffer时,读者可对buffer进行读取操作。
●当buffer中有数据时,则从其中读取一个数据,并显示然后退出。
●当buffer中没有数据时,应等待,直到buffer中有数据可读。
3、定义一个写者函数
●当有读者在占用buffer时,写者应该等待,直到所有的读者都退出为止。
●当有其他写者占用buffer时,该写者应该等待,直到占用buffer的写者退出为止。
●当buffer有空闲时,写者应该在buffer中写入一个数据并退出。
●当buffer满时,写者应该等待,直到buffer有空闲为止。
4、定义主函数,在其中可以任意创建读者与写者。
●可根据用户输入创建读者或写者进程(线程)。
5、用户界面
实验当堂所要完成事情列表:
1.调试程序使其在读者优先模式下可以运行并且能实现基本的功能得出正确的结果:能够实现读写互斥,写写互斥,读读不互斥,一个进程结束能够唤醒等待队列中的进程(先读者队列后写着队列)
2.根据实验要求完善功能:由用户决定写者向缓冲区中写入的内容,读者能够读出并显示出来;当缓冲区中没有数据时,读者要等待,直到缓冲区中有数据才能读
3.根据“读者优先”加以改变,增加一个“写者优先”模式,并且由用户来选择模式
源代码:
#include #include int rcount=0;//正在读的读者数量 int wcount=0;//写者队列中等待写操作的写者数量 int read_id=0;//读进程号 int write_id=0;//写进程号 int w=1;//读写互斥信号量 char temp[300] = {'\\0'}; int choice; //用户选择读者优先OR写者优先 int sign; //标识temp空的信号量 0表示temp空 void WFwakeup(); void RFwakeup(); struct rqueue{//读者等待队列 int readers[200]; int index; }rq; struct wqueue{//写者等待队列 int writers[200]; int index; }wq; /*void first(){ //初始化 int i; rq.index = 0; wq.index = 0; for(i = 0;i<20;i++){ rq.readers[i] = 0; wq.writers[i] = 0; } }*/ //*******************************************读进程读操作 void read(){ int i = 0; read_id++; if(rcount == 0){//当前没有读进程在读 可能有写进程在写 可能CPU空闲 if(w==1) {//如果CPU空闲,读者拿到CPU w--;// 相当于一个P操作 rcount++; if(temp[0] == '\\0'){ sign = 0; if(choice == 1){ rq.readers[rq.index++]=read_id;//将读者进程加入等待队列 RFwakeup(); return; } else{ rq.readers[rq.index++]=read_id;//将读者进程加入等待队列 WFwakeup(); return; } }//if printf("读者%d正在读\n",read_id); for(i = 0;i < 300;i++){//读取temp内容 即写者写的内容 if(temp[i] == '\\0'){ printf("\\n"); return; }//if printf("%c",temp[i]); }//for }//if else{//写者线程正在执行 printf("!有写者在写不能读!\n"); rq.readers[rq.index++]=read_id;//将读者进程加入等待队列 }//else }//if else{//rcount !=1 则知道当前已经有读者在读,读读不互斥,则这个读者可以直接进来了读 printf("读者%d正在读\n",read_id); for(i = 0;i < 300;i++){ if(temp[i] == '\\0'){ printf("\\n"); return; } printf("%c",temp[i]); }//for }//else } //***************************写进程写操作 void write(){ write_id++; if(w == 0){ if(rcount != 0 ){//有读者进程在执行 printf("!有读者在读不能写!\n"); wq.writers[wq.index++]=write_id;//将写者进程加入等待队列 wcount++; return; } if(rcount == 0 ){//rcount == 0则当前无读者,但w = 0,所以有写者在写 printf("!有写者在写不能写!\n"); wq.writers[wq.index++]=write_id;//将写者进程加入等待队列 wcount++; return; } } if(w == 1){ w--; printf("写者%d正在写\n请输入要写的内容",write_id); scanf("%s",temp); //while }//if } //************************读者优先时唤醒进程 void RFwakeup(){ int i = 0; int j = 0; int m,n; m = rq.index; // n = wq.index; if(rcount == 0){//当前无读进程,是写者在写 --》停止运行写进程 bool reader_wait=false; w=1; printf("写者已经写完\n"); sign = 1;//temp中已经有内容 要置1 for(i=0;i<=m;i++){// index为当前读者队列中的等待进程数 if(rq.readers[i]!=0){ reader_wait=true; //确实有读者在等待 printf("等待的读者%d正在读\n",rq.readers[i]); w = 0; for(j = 0;j < 300;j++){ if(temp[j] == '\\0'){ printf("\\n"); break; }//if printf("%c",temp[j]); }//for rq.readers[i]=0; rcount++; rq.index--; }//if }//for if(!reader_wait){//没有读者等待,看是否有写者等待 for(int i=0;i<=wq.index;i++){//检查写者等待队列 if(wq.writers[i]!=0){ w = 0; printf("等待的写者%d正在写\n请输入要写入的内容",wq.writers[i]); scanf("%s",temp); wq.writers[i]=0; wcount--; break; }//if }//for }//if // return; }//if else{//rcount != 0读者正在读,stop读 此时若有等待必为写者 rcount=0; w = 1; if(sign == 0){ printf("缓冲区空 等待写者\n"); return; } else{ printf("读者已经读完\n"); for(int i=0;i<=wq.index;i++){// 检查写者等待队列 if(wq.writers[i]!=0){ w = 0; printf("等待的写者%d正在写\n请输入要写入的内容",wq.writers[i]); scanf("%s",temp); wq.writers[i]=0; wcount--; break; }//if }//for } }//else } //******************************************写者优先唤醒 void WFwakeup(){ int i = 0; int j = 0; int m,n; m = rq.index; //n = wq.index; if(rcount == 0){//当前无读进程,是写者在写 --》停止运行写进程 bool writer_wait=false; w=1; printf("写者已经写完\n"); sign = 1;//temp中已经有内容 要置1 for(i=0;i<=wq.index;i++){// index为当前写者队列中的等待进程数 if(wq.writers[i]!=0){ writer_wait=true; //确实有写者在等待 printf("等待的写者%d正在写\n 请输入要写的内容\n",wq.writers[i]); w = 0; scanf("%s",temp); wq.writers[i]=0; wcount--; break; } } if(!writer_wait){//没有xie者等待,看是否有du者等待 for(int i=0;i<=m;i++){//检查写者等待队列 if(rq.readers[i]!=0){ w = 0; printf("等待的读者%d正在读\n",rq.readers[i]); for(j = 0;j < 300;j++){ if(temp[j] == '\\0'){ printf("\\n"); rq.index--; break; }//if printf("%c",temp[j]); }//for rq.readers[i]=0; rcount++; }//if }//for }//if // return; }//if else{//rcount != 0读者正在读,stop读 此时若有等待必为写者 rcount=0; w = 1; printf("读者已经读完\n"); for(int i=0;i<=wq.index;i++){// 检查写者等待队列 if(wq.writers[i]!=0){ w = 0; printf("等待的写者%d正在写\n请输入要写入的内容",wq.writers[i]); scanf("%s",temp); wq.writers[i]=0; wcount--; break; }//if }//for } } void menu1(){ char i; printf(" 1-创建读者进程\n 2-创建写者进程\n 3-结束当前执行的进程\n 4-退出程序\n"); printf("*******************************************\\n"); do{ printf("当前队列中有读者: %d个 写者: %d个\n",rq.index,wcount); printf("*******************************************\\n"); printf(" ----->"); scanf("%s",&i); switch(i){ case '1': read(); break; case '2': write(); break; case '3': RFwakeup(); break; case '4': exit(0); default: printf("输入错误请重新输入\n"); } }while(true); } void menu2(){ char i; printf(" 1-创建读者进程\n 2-创建写者进程\n 3-结束当前执行的进程\n 4-退出程序\n"); printf("*******************************************\\n"); do{ printf("当前队列中有读者: %d个 写者: %d个\n",rq.index,wcount); printf("*******************************************\\n"); printf(" ----->"); scanf("%s",&i); switch(i){ case '1': read(); break; case '2': write(); break; case '3': WFwakeup(); break; case '4': exit(0); default: printf("输入错误请重新输入\n"); } }while(true); } void main(){ printf("**************************************************************************\\n"); printf(" 20092104实验一\n 1.读者优先\n 2.写者优先\n"); scanf("%d",&choice); while(1){ if(choice == 1) menu1(); if(choice == 2) menu2(); if(choice != 1 && choice != 2){ printf("输入错误请重新输入\n"); scanf("%d",&choice); } } } 实验流程图: N Y N N Y Y Y N N Y Y N N Y Y N N Y 有读者说明若有等待必为写者 Y N N Y Y N Y N 核心部分设计思路: 分别用两个队列来存放等待的读者进程和写者进程,一个进程结束后就要将因他阻塞的进程唤醒,如果是读者优先,则先检查读者进程,如果发现读者进程不为空,就进行读操作,直到读者进程为空,才进行写操作;同理,如果是写者优先,则先检查写进程,如果发现写者进程不为空,就进行写操作,直到写者进程为空,才进行读操作。 读写互斥:只有当互斥信号量w = 1并且当前读者数为0时,才可以进行写操作,对于读进程,w = 1且当前读者数为0时,第一个读进程进行读操作,当当前读者数大于0时,不用判断互斥信号量w而直接进行读操作。对缓冲区为空的情况,如果缓冲区为空时进行读操作,则会设置一个信号量标志缓冲区为空sign = 0,不可以进行读操作,释放出CPU,调用唤醒函数唤醒写进程,此时会将此进程放入读者进程等待队列中等待写者来写数据再进行读操作,写者写完数据,缓冲区不空时,将标志置为缓冲区不空sign = 1。 编译过程中遇到的问题: 1.编译过程中出现的一个很大的错误就是在构造队列的时候给变量index赋值了,系统提示错误 解决:取消赋值,编写了一个初始化函数进行初始化,后来发现不初始化也没用什么问题,就注释掉了。 2.刚开始还会出现一些括号不匹配的错误,在后来的程序完善过程中,尽量给后括号做一个注释进行标识。 运行过程中遇到的问题: 1.不能接受非法字符,如果输入非法字符就会进入死循环,但是在switch外明明有检查语句。 解决:通过单步跟踪,发现检查语句并没有起到作用,于是调整了一下结构,将检查语句放到switch里,既起到了检查的作用,同时也减少了代码量。 2.在读者进行读取数据时,总是会在读取完写者写的内容的后面出现一个小a。 解决:也是通过单步跟踪,发现是判空和输出的顺序问题,如果先输出后判空的话,会多进行一次循环,导致输出一个小a,但是现在还是不太明白为什么多输出的是小a。 3.当缓冲区为空的时候创建了n-1个读进程,他们都在等待写进程写数据,然而等写进程写完数据后,这些读进程并没有进行读取,继续创建读进程,就会发现是读进程n在进行读取。 解决:刚开始是以为参数设置问题,后来仔细一想,发现是没有在调用唤醒函数前没有将读进程放入读者等待队列中,于是在调用唤醒函数前,添加将读者进程放入等待队列的语句。 4. 还出现了不少小错误,例如将i,j写错导致读者每次都会读300次用一个字母,没有设置信号量sign导致在缓冲区为空的时候还没有进行读操作就会有“读者已经读完”的错误提示等等。也有一些原则性错误比如在唤醒函数中,读或者写进程结束后,将信号量置1,但是在队列中选择进程进行操作的时候,没有把信号量置0,导致了执行唤醒函数后进程不互斥的问题。开始的程序中还设置了一些意义不大的变量,比较混乱,后来通过画简单的流程图,将意义不大的变量取消,理清了思路,解决了问题。 运行过程中的问题出现的问题比较多,但都不是特别大的问题,通过调试分析都得到了解决。 实验收获与心得: 这次实验耗时比较多,预习的时候并没有准备特别充分,程序还有很多问题,功能也不没有达到老师的要求。实验过程中,一步一步由简到繁,完善程序的同时,问题也出现了不少,通过画简单的流程图,通过单步跟踪,通过分析理解,都得到了解决。程序的缺点就是设置的变量过多,后来经过分析和调试,发现很多都是没有必要的,还让程序更加混乱,条理不清楚,后来去掉了不少。这次实验让我更加深刻的理解了进程的信号量的概念和应用,在不断出现错误,发现错误,改正错误的过程中,对互斥信号量有了非常深刻的理解。虽然程序的功能还不是很完善,但是自己调试成功的程序就非常有成就感。下次实验我会提前做好充分的预习,争取做的更好。
