3.完整程序
#include <ctype.h> #include <stdlib.h> #include <time.h> #include <cstring> #include <iostream> using namespace std; struct PCB {//进程控制块 string name;//进程名 int status; // 1 就绪, 2 等待,3 完成,0 正在运行 int waitreason;//等待原因 int breakingpoint;//断点 }; PCB producer;//生产者进程 PCB consumer;//消费者进程 PCB* currp;//当前进程 int s1;//信号量:生产者可以往缓冲区放物品 int s2;//消费者可以从缓冲区拿 char c;//生产者生产的字符 char x;//消费者消费的字符 int pc;//程序计数器 int in;//生产者放的位置 int out;//消费者拿的位置 int pa[5];//生产者的程序 char B[10];//拿 int sa[5];//消费者程序 void init() { s1 = 10; s2 = 0;//初始化信号量 out = 0; in = 0;//初始化生产者与消费者在缓冲区放的商品的位置 producer.name = "producer"; producer.status = 1; producer.waitreason = 0; producer.breakingpoint = 0;//pcb状态为就绪,断点为0 consumer.name = "consumer"; consumer.status = 1; consumer.waitreason = 0; consumer.breakingpoint = 0;//pcb状态为就绪,断点为0 currp = &producer;//将现行进程设置为生产者进程 pc = 0;//pc=0 for (int i = 0; i <= 4; i++) { pa[i] = i; sa[i] = i;//初始化生产者程序与消费者程序 } } void p(int& s, int p, PCB* pcb) { s = s - 1; cout << "mutex s" << p<<endl; if (s < 0) { pcb->status = 2;//等待状态 pcb->waitreason = p;//等待的信号量是p cout << pcb->name << " begins waiting!\n"; } else { cout << pcb->name << " doesn't need waiting!\n"; pcb->status = 1;//将调用p(s)的进程置为就绪 } } void v(int& s, int p, PCB* pcb) { s = s + 1; if (s <= 0) { if (producer.waitreason == p) producer.status = 1; else if (consumer.waitreason == p) consumer.status = 1;//将一个等待s信号量的进程置为就绪 if (currp->name == "producer") cout << "consumer released!"; else cout << "producer released!"; } pcb->status = 1;//将调用V(s)的进程置为就绪 } void printstate() {//打印当前状态 cout << "*************************************************\n"; cout << "current process" << currp->name << endl; cout << "producer breakingpoint" << producer.breakingpoint << endl; cout << "producer status" << producer.status << endl; cout << "producer waitreason" << producer.waitreason << endl; cout << "consumer breakingpoint" << consumer.breakingpoint << endl; cout << "consumer status" << consumer.status << endl; cout << "consumer waitreason" << consumer.waitreason << endl; cout << "*************************************************\n"; } void exec() {//模拟处理器指令执行 int i = pc; int j; int curq; cout << "current process " << currp->name << endl; if (currp->name == "producer") {//现行进程为生产者? j = pa[i]; curq = 0; } else { j = sa[i]; curq = 1; } pc = i + 1; switch (j) {//按j转向各模拟指令对应的过程 case 0: if (curq == 0) {//produce char pronum; cout << "please produce a char"; cin >> pronum; c = pronum; cout << "produced!"; currp->status = 1; } else p(s2, 2, currp);//p(s2) break; case 1: if (curq == 0) p(s1, 1, currp);//p(s1) else {//GET cout << "get goods!"; x = B[out]; out = (out + 1) % 10; cout << x << endl; currp->status = 1; } break; case 2: if (curq == 0) {//PUT cout << "put goods!"; B[in] = c; in = (in + 1) % 10; cout << c << endl; currp->status = 1; } else v(s1, 1, currp);//V(s1) break; case 3: if (curq == 0)//V(s2) v(s2, 2, currp); else {//consume cout << "goods consumed!"; cout << x << endl; currp->status = 1; } break; case 4://goto 0 pc = 0; currp->status = 1; cout << "goto 0!"; break; } if (producer.status != 3) {//模拟时采用人工选择的办法实现“生产者运行结束”的判断 cout << "Has producer finished?Y/N"; char st; cin >> st; if (st == 'Y') producer.status = 3; } return; } void cpuwork() { while (1) { currp->breakingpoint = pc;//当前进程pcb断点为pc if (consumer.status != 1 && producer.status != 1) return;//无就绪进程 if (consumer.status == 1 && producer.status == 1) { srand(time(NULL));//随机选择一个就绪进程 int randnum = rand() % 2; if (randnum >= 1) currp =& consumer; else currp =& producer; /* int chooseprocess; cout << "choose a process:1.producer 2.consumer"; cin >> chooseprocess; if (chooseprocess == 1) currp = &producer; else currp = &consumer;*/ } else if (consumer.status == 1) currp = &consumer; else if(producer.status==1) currp = &producer; currp->status = 0;//将现行进程改为运行态 pc = currp->breakingpoint;//现行进程pcb断点值赋值给pc exec(); } } int main() { init(); cpuwork(); return 0; }
五、运行结果
本部分对应实验报告要求中的第(3)部分:从键盘上输入一组字符,由生产者每次读入一个字符供消费者输出。运行模拟程序,打印依次读入的字符和消费者输出的字符。
开始时生产者与消费者都是处于就绪态。系统随机选择了消费者处于现行进程。消费者执行P(s 2 s_2s2)操作,将自己阻塞并等待s 2 s_2s2信号。
只有生产者就绪,选择生产者作为现行进程,生产者正确地生产了一个字符”c”。
只有生产者就绪,选择生产者作为现行进程,生产者正确地将生产完毕的字符放入缓冲区供消费者去消费。
生产者执行V操作,s 2 s_2s2信号量表示生产者告诉消费者能从缓冲区取物品。生产者将消费者进程释放。
以此类推,生产者完成第一个字符的生产后,跳转回第一个程序过程produce,并生产第二个字符b。
消费者在被唤醒后处于就绪态。当随机选择进程选择到消费者时,消费者正确地取得了生产者生产的第一个字符”c”。
消费者正确地将字符”c”消费掉。
与此同时,生产者正确地放入第二个生产的字符”b”。
生产者生产完”b”后,将自己置为完成态。生产者正确地完成了所有的生产。此时消费者执行剩下的过程,正确地取得了生产者的第二个字符”b”,并正确地消费掉。
综上所述,实验题目被正确地实现了。生产者依次读入字符,消费者按顺序依次输出字符。
六、实验思考
本部分对应实验报告要求第(4)部分:把生产者和消费者进程中的P操作、V操作都改成空操作指令,观察在两者不同步的情况下可能出现的与时间有关的错误。打印依次读入的字符和消费者输出的字符。
言而简之,两者在不同步的情况下可能出现与时间有关的错误。不仅有消费者会出现错误,而且生产者也会出现错误。
实验要求中,不允许消费者在空缓冲器内取产品。若将P(s 2 s_2s2)改为空操作,则消费者不检测s 2 s_2s2信号量。s 2 s_2s2信号量本身含义是生产者是否允许消费者从缓冲器取产品。若缓冲器为空,则生产者不允许消费者取产品。由于P操作被改为空操作,所以消费者进行了不被允许的操作,从空缓冲区取产品。但缓冲区为空,消费者没有取得任何商品。而且消费者错误地消费了并没有取得到的商品。这就是消费者会产生的错误。
实验要求中,也不允许生产者将产品放入已满的缓冲器内。缓冲器buffer的大小为10。若按图5的处理器程序调度流程,每次随机选择现行进程,其实触发这一类错误的概率不大。但为了故意触发这一类错误,我们将流程图与代码稍加修改,每次不是随机选择现行就绪进程而是手动从键盘输入选择现行就绪进程。
缓冲区长度为10,放满了10个字符分别为a、b、c、d、e、f、g、h、i、j。生产者生产完毕第11个字符k字符。若P(s 1 s_1s1)不为空操作,则生产者在生产完字符后就会检测缓冲区剩余空间已为0。则生产者不能将刚生产出的k放入缓冲区,则必须等待消费者把先一个字符拿到并消费掉。但由于P(s 1 s_1s1)被改为空操作,所以生产者在执行put过程时就会发生错误。