操作系统课设生产者消费者问题

课 程 设 计课程设计名称:操作系统原理课程设计专 业 班 级 : 计科F 1803 学 生 姓 名 : 学 号 : 指 导 教 师 : 课程设计时间:2020.6.1—2020.6.12 计算机科学与技术 专业课程设计任务书学生姓名专业班级计科1803学号题 目A——生产者消费者问题课题性质其它课题来源自拟课题指导教师同组姓名主要内容目的:掌握信号的使用方法和PV操作的定义,掌握使用PV操作实现进程之间同步与互斥的方法,加深对进程同步互斥概念的理解。基本原理和算法:利用互斥与同步中的信号量;使用信号量解决有限缓冲区生产者和消费者问题。模块介绍:(两大模块)生产者和消费者,生产者又包括Produce(),Append();消费者包括Take(),Consume();线程的创建。任务要求设计要求:设计一程序,由一个进程创建三个子进程,三个子进程一个是生产者进程,两个是消费者进程,父子进程都使用父进程创建的共享存储区进行通信,由生产者进程将一个数组中的十个数值发送到由5个缓冲区组成的共享内存中,两个消费者进程轮流接收并输出这十个数值,同时将两个消费者进程读出的数值进行累加求各和。参考文献[1] Cays. Horstmann,Gary Cornell编著. JAVA核心技术卷I. 北京:机械工业出版社, 2008.[2]郑莉编著. Java语言程序设计(第二版) . 北京:清华大学出版社, 2011.[3]吕国英等编著.算法设计与分析.北京:清华大学出版社,2009.[4]马小军等编.软件工程项目案例与实践指导.北京: 清华大学出版社,2013.[5]汤小丹等编著.计算机操作系统(第四版).西安电子科技大学出版社,2014.审查意见指导教师签字:教研室主任签字: 年 月 日 说明:本表由指导教师填写,由教研室主任审核后下达给选题学生,装订在设计(论文)首页目 录 目 录 11 需求分析 22 概要设计 23 运行环境 24 开发工具和编程语言 35 详细设计 35.1程序框架 35.1.1生产者与消费者的父类 35.1.2生产者类 45.1.3消费者类 45.1.4主程序入口 55.2基本算法分析 55.2.1 wait(mutex)与signa1()数值信号 55.2.2 isBufferEmpty()与isBufferFull()信号 55.2.3 produce()存数方法与consume()取数方法 76 调试分析 86.1不启动生产者线程测试结果分析 86.2不启动消费者线程测试结果分析 87 测试结果 9参考文献 10心得体会 111 需求分析生产者-消费者问题是一个著名的进程同步问题。既然有进程间的同步,也就必将涉及到进程之间的互斥与通信问题,对于这个问题的解决有着很强的现实意义。它的现实意义在于可以类比到计算机中对于临界资源的互斥共享。生产者与消费者就好比是对计算机临界资源访间的程序或用户,而临界资源如打印机、磁带机等设备。2 概要设计图2.1程序流程图所示,首先生产者与消费者线程创建,便就去访问缓冲区。对于生产者,若缓冲区没有被其他线程访问,且缓冲区未满则生产数据存放到缓冲区。若其中有条件没有满足,则生产者线程进入阻塞状态。而对于消费者同样也需要缓冲区没有被其他线程访问,但同时要求缓冲区未空才能从缓冲区取数据,若其中有一个条件未满足,同样进入阻塞状态。图2.1 程序流程图3 运行环境运行环境:Windows 104 开发工具和编程语言开发工具:Eclipse IDE 2019-12编程语言:JAVA5 详细设计5.1程序框架5.1.1生产者与消费者的父类public class SuperThread extends Thread{ private Lock bufferLock=new ReentrantLock(); //定义缓冲区的读写锁public static IntBuffer buffer=IntBuffer.allocate(5); //共享缓冲区public int array[]={1,5,6,9,8,11,13,10,7,3}; //生产者数组public static int count=0; //记录生产者执行的次数public static int consumerSum=0; //两个消费者取出数据的累加public static int consumerCount=0; //记录消费者取数次数private static int consumerData; //存放从缓冲区中取得的数据 public String nameString; //定义线程名public static int mutex = 0;//线程互斥信号,0生产者进程,1消费者进程/构造函数/public SuperThread (String name) {} //主要完成一些变量的初始化/获得线程名/public String getNameString() {}/获得互斥信号/public static int getMutex(){}/设置互斥信号/public static void setMutex(int mutex){ }/判断是否有线程访问缓冲区,没有则对线程进行加锁/public boolean wait(int mutex) {}/释放进程操作完成信号,其实本质就是修改mutex的信号值/public static void signal(){}/判断缓冲区是否为空/public boolean isBufferEmpty() {//根据缓冲区中是否有0存在来判断是否为空//若0的个数为5则为空,反之则不为空}/判断缓冲区是否为满/public boolean isBufferFull(){//根据缓冲区中是否有0存在来判断是否为空//若0的个数为0则为满,反之则不满}/消费者取得缓冲区数据/public int Take(int index) {}//完成取数操作/生产者向公共缓冲区放数据/public void Append(int index){} //完成向缓冲区存数操作5.1.2生产者类public class Producer extends SuperThread implements Runnable{/生产者构造函数/public Producer (String name){super(name); }/线程中的run函数,线程启动时默认调用的函数/public void run(){//线程启动后向缓冲区存数操作}5.1.3消费者类public class Consumer extends SuperThread implements Runnable{/消费者构造函数/public Consumer (String name){super(name);}/线程中的run函数,线程启动时默认调用的函数/public void run(){ //线程启动后向缓冲区取数操作}5.1.4主程序入口主函数main(String[] args) {//生产者与消费者对象定义为:生产者、消费者1、消费者2//创建线程producerThread、consumer1Thread、consumer2Thread//启动线程producerThread.start()、consumer1Thread.start()、// consumer2Thread.start();5.2基本算法分析5.2.1 wait(mutex)与signa1()数值信号数值信号wait(mutex)主要是通过整型值mutex的设置来表示缓冲区是否已被访问,具体过程为:当有线程需要访问缓冲区时,先确定wait (mutex)信号值,若mutex的值为1则wait(mutex)为真,便可进行下一步操作,若mutex的值为0则wait(mutex)为不为真,该线程阻塞。当线程完成对缓冲区的访问后需要调用signal()信号对信号值mutex进行释放,释放后mutex的值为1,以便其他线程能够访问缓冲区。wait(mutex)与signal()的主要代码如下:public boolean wait(int mutex){ //wait(mutex)信号if (mutex=1){return false;}else{return true;} }public static void signal() { //signal()信号:mutex = 0; }5.2.2 isBufferEmpty()与isBufferFull()信号对于以上所述的wait(mutex)信号还不足以控制线程之间的同步与互斥,还必须使用到isBufferEmpty()与isBufferFull()两个信号。对于生产者要用到信号isBufferFull()来判断缓冲区是否为满,当wait(mutex)型号值为真且isBufferFull()信号值不为真时,生产者线程才能对缓冲区进行存数操作,否则就阻塞等待机会。而对于消费者同样也要用到辅助信号isBufferEmpty()来判断缓冲区是否为空,只有缓冲区不为空且wait(mutex)为真时,消费者才能对缓冲区进行取数操作。isBufferEmpty()与isBufferFul1()的主要代码如下:public boolean isBufferEmpty(){ //判断缓冲区是否为空int count = 0;for(int i=0;i0){ count++; } }if (count5){ return true;}else{return false; }}public boolean isBufferFull(){ //判断缓冲区是否为满int count = 0;for (int i=0; itrue.produce()与consume()方法的代码框架如下:/生产数,即在缓冲区中存数/ public void produce(){ if(wait(mutex) && !isBufferFu11()){ //判断是否访问缓冲区bufferLock.lock(); //对该程序段加锁,防止其他线程访问缓冲区try{//设置互斥信号//向缓冲区存数//释放互斥信号}finally{ //解锁} }}/消费数,即在缓冲区中取数/public void consume() {if (wait (mutex) && !isBufferEmpty()) { //判断是否访问缓冲区//加锁bufferLock.lock();try {//设置互斥信号//从缓冲区取数//将取数后的缓冲区置0//释放互斥信号}finally{//解锁} }}6 调试分析6.1不启动生产者线程测试结果分析由于没有启动生产者线程,所以缓冲区为空,从而导致isBufferEmpty()信号为真,所以消费者线程不可访问缓冲区取数。“程序测试结果如图6.1所示。”图6.1 6.2不启动消费者线程测试结果分析由于没有启动消费者线程,而只启动了生产者线程,所以在缓冲区未满前生产者一直向缓冲区生产数据直到缓冲区满为止。由于没有消费者消费数据,所以缓冲区满后生产者不能向缓冲区生产数据。“程序测试结果如图6.2所示。”图6.27 测试结果“程序运行结果如图7.1所示。” 图7.1 “程序运行结果如图7.2所示。” 图7.2 参考文献[1] Cays.Horstmann,Gary Cornell编著. JAVA核心技术卷I. 北京:机械工业出版社,2008.[2]郑莉编著. Java语言程序设计(第二版). 北京:清华大学出版社,2011.[3]吕国英等编著. 算法设计与分析. 北京:清华大学出版社,2009.[4]马小军等编. 软件工程项目案例与实践指导. 北京:清华大学出版社2013.[5]汤小丹等编著. 计算机操作系统(第四版). 西安电子科技大学出版社,2014. 心得体会无论是在操作系统这门]课程,还是在其它的课程中只要讲到线程就必定会提到“生产者与消费者”问题。因为就这个问题能够很好地模拟线程间的同步、异步以及线程间的通信,而且易于学生理解线程关系与联系。在本次课程设计中通过自己编写代码实现“生产者-消费者”算法,其中主要的算法思想体现在如何通过信号函数来控制线程的执行。在算法的编写过程中遇到了许多的小问题,但在不断的调试和查阅资料得到了解决,这让我对线程有了更深的了解,明白了操作系统是如何通过各种机制来实现线程之间有条不紊的执行。最后我还想说,进入大学后每个学期都有课程设计,独自完成课程设计题目时,对于设计过程中出现的问题,和朋友交流和讨论是很有效的。能及时的将问题解决,我想这一点不论是在今后的生活还是工作中都会有很大的帮助。
主程序入口:
package OSdesign;public class Main { public static void main(String[] args) { Producer producer = new Producer(“生产者”);//定义生产者对象,通过实现Runnable类的对象来开辟第一个线程 Consumer consumer1 = new Consumer(“消费者1”);//定义消费者对象1,开辟第二个线程 Consumer consumer2 = new Consumer(“消费者2”);//定义消费者对象2,开辟第三个线程 Thread producerThread=new Thread(producer); //调用start()方法开启线程 Thread consumer1Thread=new Thread(consumer1); Thread consumer2Thread=new Thread(consumer2); producerThread.start(); consumer1Thread.start(); consumer2Thread.start(); } }/*producer和consumer中run方法只是执行superthread中prduce和consume方法, * 执行producer线程后貌似执行consumer1线程时总是break; */父进程:package OSdesign;import java.nio.Buffer;import java.nio.IntBuffer;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;public class SuperThread extends Thread{ private Lock bufferLock = new ReentrantLock(); //定义缓冲区的读写锁 public static IntBuffer buffer=IntBuffer.allocate(5); //共享缓冲区 public int array[]={1,5,6,9,8,11,13,10,7,3}; //生产者数组 public static int count = 0; //记录生产者执行的次数 public static int consumerSum =0; //两个消费者取出数据的累加 public static int consumerCount =0; //记录消费者取数次数 private static int consumerData; //存放从缓冲区中取得的数据 public String nameString; //定义线程名 /线程互斥信号,值为0表示生产者进程,值为1表示消费者进程/ public static int mutex = 0; public SuperThread () { } /构造函数/ public SuperThread (String name) { nameString=name; } /获得线程名/ public String getNameString() { return nameString; } /获得互斥信号/ public static int getMutex(){ return mutex; } /设置互斥信号/ public static void setMutex(int mutex){ SuperThread.mutex =mutex; } /判断是否有线程访问缓冲区,没有则对线程进行加锁/ public boolean wait(int mutex) { //wait(mutex)信号 if (mutex1){ return false; }else{ return true; } } /释放进程操作完成信号,其实本质就是修改mutex的信号值/ public static void signal(){ //signal()信号 mutex =0; } public boolean isBufferEmpty(){ //判断缓冲区是否为空 int count=0; for(int i=0;i0){ //根据缓冲区中的 0的个数来判断,若缓 //区0的个数为5(缓冲区容量为5),则为空反之不为空 count ++; } } if (count5) { return true; }else{ return false; } } public boolean isBufferFull(){ //判断缓冲区是否为满 int count=0; for(int i=0;i生产数,即向缓冲区中存数/ public void produce(){ //if mutex!=1(生产者进程进入缓冲区) if(wait(mutex) && !isBufferFull()) { //判断是否访问缓冲区(不满) bufferLock.lock(); //对该程序段加锁,防止其他线程访问缓冲区 try { setMutex(1);//将线程互斥信号值mutex设置为1,使之互斥 int index = 0; for (int i=0;i消费数,即在缓冲区中取数/ public void consume(){ //if mutex!=1(生产者进程进入缓冲区), if(wait(mutex) && !isBufferEmpty()){ //判断是否访问缓冲区 bufferLock.lock(); //对该程序段加锁,防止其他线程访问缓冲区 try{ setMutex(1) ;//将线程互斥信号值mutex设置为1,使之互斥 int index=0;//存放顺序查找第一个为 0的数的下摆 int i; for(i=0;i0){ index = i; break; }else if(buffer.get(i)>0){ //缓冲区为满的情况 index = 5; } } consumerData=Take(index-1); //从缓冲区取数据 consumerCount++; System.out.println(“第”+consumerCount+“次”+“消费者取出的数为”+index); if(consumerCount<=2){ consumerSum=consumerSum+consumerData; //计算两次取数之和 if(consumerCount2){ System.out.println(“消费者取数之和为:”+consumerSum); consumerSum=0; consumerCount=0; } } buffer.put(index-1,0); //将已取过数的缓冲区重新置0 consumerPrintBufferData(); //输出取数后的缓冲区 signal(); //释放互斥信号,将mutex设置为0 }finally{ bufferLock.unlock();//解锁 } } // else // System.out.println(“消费者—说:生产者还没有生产数据,缓冲区为空!”); } /消费者取得缓冲区数据/ public int Take(int index){ return buffer.get(index); //完成取数操作 } /生产者向公共缓冲区放数据/ public void Append(int index){ buffer.put(array[index]); //完成向缓冲区存数操作 } public void producePrintBufferData(){ System.out.println(“生产者–存数后的缓冲区数据为:”); // while(buffer.remaining>0) { // System.out.println(buffer.get()); // } } public void consumerPrintBufferData() { System.out.println(“消费者–取数后的缓冲区数据为:”); }}消费者类:package OSdesign;/实现Runnable接口,复写run方法/ public class Consumer extends SuperThread implements Runnable{ /消费者构造函数/ public Consumer (String name){ super(name); } /线程中的run函数,线程启动时默认调用的函数/@Override public void run(){ //复写run方法 while (true){ //线程启动后向缓冲区取数操作 // for(int index=0;index<10;) { consume(); // System.out.println(getNameString() +“取出数据为:”+index++); // } try { sleep(1000); //休眠1秒,避免太快导致看不到同时执行 } catch (InterruptedException e) { e.printStackTrace(); } } }}生产者类:package OSdesign;/实现Runnable接口,复写run方法/ public class Producer extends SuperThread implements Runnable{ public Producer (String name){ /生产者构造函数/ super(name); } /线程中的run函数,线程启动时默认调用的函数/ public void run(){ //线程启动后向缓冲区存数操作 while (true){ //for(int i=0;i<10;) { produce(); // System.out.println(getNameString()+“存入数据为:”+i++); // } try { sleep(1000); //休眠1秒,避免太快导致看不到同时执行 } catch (InterruptedException e) { e.printStackTrace(); } } }}

你可能感兴趣的:(笔记)