课程设计报告
课程名称: 操作系统
专业 计算机科学与技术
学生姓名 班学
级 号
指导教师 完成日期
信息工程学院
题目:生产者-消费者问题的模拟实现
一、设计目的
本课程设计是学习完“操作系统原理”课程后进行的一次全面的综合训练,通过课程设计,更好地掌握操作系统的原理及实现方法,加深对操作系统基础理论和重要算法的理解,加强学生的动手能力。
二、设计内容
(1)概述
设计目的:通过研究Linux 的进程机制和信号量实现生产者消费者问题的并发控制。
说明:有界缓冲区内设有20个存储单元,放入/取出的数据项设定为1-20这20个整型数。
设计要求:(1)每个生产者和消费者对有界缓冲区进行操作后,即时显示有界缓冲区的全部内容,当前指针位置和生产者/消费者县城的标识符。(2)生产者和消费者各有两个以上。(3)多个生产者或多个消费者之间须有共享对缓冲区进行操作的函数代码。 (2)设计原理
通过一个有界缓冲区把生产者和消费者联系起来。假定生产者和消费者的优先级是相同的,只要缓冲区未满,生产者就可以生产产品并将产品送入缓冲区。类似地,只要缓冲区未空,消费者就可以从缓冲区中取走产品。应该禁止生产者向满的缓冲区送入产品,同时也应该禁止消费者从空的缓冲区中取出产品,这一机制有生产者线程和消费者线程之间的互斥关系来实现。与计算打印两进程同步关系相同,生产者和消费者两进程P和C之间应满足下列两个同步条件:
① 只有在缓冲池中至少有一个缓冲区已存入消息后,消费者才能从中提取信息,
否则消费者必须等待。
② 只有缓冲池中至少有一个缓冲区是空时,生产者才能把消息放入缓冲区,否则
生产者必须等待。
为了满足第一个同步条件,设置一个同步信号量full,它代表的资源是缓冲区满,它的初始值为0,它的值为n时整个缓冲池满。这个资源是消费者类进程C所有,C进
1
程可以申请该资源,对它施加P操作,而C进程的合作进程生产者进程P对它施加V操作。同样为了满足第二个同步条件,设置另一个同步信号量empty,它代表的资源是缓冲空区,它的初始值为n,表示缓冲池中所有缓冲区空。信号量full表示可用缓冲区数量,信号量empty表示缓冲区数量,设置整型变量:存入指针in和取出指针out。
为解决生产者/消费者问题,应该设置两个资源信号量,其中一个表示空缓冲区的数目,用g_hFullSemaphore表示,其初始值为有界缓冲区的大小SIZE_OF_BUFFER;另一个表示缓冲区中产品的数目,用g_hEmptySemaphore表示,其初始值为0.另外,由于有界缓冲区是一个临界资源,必须互斥使用,所以还需要在设置一个互斥信号量g_hMutex,初始值为1.
P原语的主要动作是:
① sem减1;
② 若sem减一后仍大于或等于零,则进程继续执行;
③ 若sem减一后小于零,则该进程被阻塞后入与该信号相对应的队列中,然后转进程调度。 V原语的操作主要动作是:
① sem加1;
② 若相加结果大于零,进程继续执行;
③若相加结果小于或等于零,则从该信号的等待队列中唤醒一等待进程然后再返回原进程继续执行或转进程调度。
采用的同步方法:
1)利用函数CreateMutex(NULL,FALSE,NULL)创建互斥信号量g_hMutex,表示缓冲区当前的状态,若为true时,则表示缓冲区正被别的进程使用。三个参数表示的意义分别为:指向安全属性的指针,初始化互斥对象的所有者,指向互斥对象名的指针。
2)利用函数CreateSemaphore(NULL,SIZE_OF_BUFFER-1,SIZE_OF_BUFFER-1, NULL)创建缓冲区满的信号量g_hFullSemaphore,值为true时表示缓冲区已满。四个参数分别为:表示是否允许继承、设置信号机的初始计数、设置信号机的最大计数、指定信号机对象的名称(-1是因为计数从开始)。
2
3)利用函数CreateSemaphore(NULL,0,SIZE_OF_BUFFER-1,NULL)创建缓冲区空的信号量g_hEmptySemaphore,该值为true时表示缓冲区为空。 5、数据定义及其详细解释
const unsigned short SIZE_OF_BUFFER = 20; //缓冲区长度 unsigned short ProductID = 0; //产品号
unsigned short ConsumeID = 0; //将被消耗的产品号 unsigned short in = 0; //产品进缓冲区时的缓冲区下标 unsigned short out = 0; //产品出缓冲区时的缓冲区下标 int g_buffer[SIZE_OF_BUFFER]; //缓冲区是个循环队列 bool g_continue = true; //使程序跳出循环,控制程序结束
HANDLE g_hMutex; //用于线程间的互斥
HANDLE g_hFullSemaphore; //当缓冲区满时迫使生产者等待 HANDLE g_hEmptySemaphore; //当缓冲区空时迫使消费者等待 DWORD WINAPI Producer(LPVOID); //生产者线程 DWORD WINAPI Consumer(LPVOID); //消费者线程
(3)详细设计及编码 流程图 生产者:
入口 sem=sem-1 s>=0 否 调用进程入等待队列 是 返回 转进程调度
3
消费者: 程序清单 1.存储结构定义 利用信号量解决生产者消费者问题 const unsigned short SIZE_OF_BUFFER = 10; //缓冲区长度 unsigned short ProductID = 0; //产品号 unsigned short ConsumeID = 0; //将被消耗的产品号 unsigned short in = 0; //产品进缓冲区时的缓冲区下标 unsigned short out = 0; //产品出缓冲区时的缓冲区下标 int g_buffer[SIZE_OF_BUFFER]; //缓冲区是个循环队列 bool g_continue = true; //控制程序结束 HANDLE g_hMutex; //用于线程间的互斥 HANDLE g_hFullSemaphore; //当缓冲区满时迫使生产者等待 HANDLE g_hEmptySemaphore; //当缓冲区空时迫使消费者等待 DWORD WINAPI Producer(LPVOID); //生产者线程 DWORD WINAPI Consumer(LPVOID); //消费者线程 2.算法相关的函数 (1)创建各个互斥信号以及生产者线程和消费者线程的函数在如下主函数里面所示: int main() { 返回或转进程调度 S<=0 是 唤醒等待队列中的一个进程 返回 sem=sem-1 否 入 口
4
//创建各个互斥信号
g_hMutex = CreateMutex(NULL,FALSE,NULL); g_hFullSemaphore
CreateSemaphore(NULL,SIZE_OF_BUFFER-1,SIZE_OF_BUFFER-1,NULL); g_hEmptySemaphore = CreateSemaphore(NULL,0,SIZE_OF_BUFFER-1,NULL); //调整下面的数值,可以发现,当生产者个数多于消费者个数时, //生产速度快,生产者经常等待消费者;反之,消费者经常等待。 const unsigned short PRODUCERS_COUNT = 3; //生产者的个数 const unsigned short CONSUMERS_COUNT = 1; //消费者的个数 //总的线程数
const unsigned short THREADS_COUNT PRODUCERS_COUNT+CONSUMERS_COUNT;
HANDLE hThreads[PRODUCERS_COUNT]; //各线程的handle DWORD producerID[CONSUMERS_COUNT]; //生产者线程的标识符 DWORD consumerID[THREADS_COUNT]; //消费者线程的标识符 //创建生产者线程 for (int i=0;i {
hThreads[i]=CreateThread(NULL,0,Producer,NULL,0,&producerID[i]); if (hThreads[i]==NULL) return -1; }
//创建消费者线程 for ( i=0;i {
hThreads[PRODUCERS_COUNT+i]=CreateThread(NULL,0,Consumer,NULL,0 &consumerID[i]);
if (hThreads[i]==NULL) return -1; }
while(g_continue) {
if(getchar())
{ //按回车后终止程序运行 g_continue = false; }} return 0; }
5
(2) 生产者生产一个产品的函数:
//生产一个产品。简单模拟了一下,仅输出新产品的ID号 void Produce() {
std::cerr << \"Producing \" << ++ProductID << \" *** \"; std::cerr << \"Succeed\" << std::endl; }
(3)把新生产的产品放入缓冲区的函数: //把新生产的产品放入缓冲区 void Append() {
std::cerr << \"Appending a product *** \"; g_buffer[in] = ProductID; in = (in+1)%SIZE_OF_BUFFER;
std::cerr << \"Succeed\" << std::endl; }
(4)输出缓冲区当前的状态的函数: //输出缓冲区当前的状态 for (int i=0;i {
std::cout << i <<\": \" << g_buffer[i]; if (i==in)
std::cout << \" <-- 生产\"; if (i==out)
std::cout << \" <-- 消费\"; std::cout << std::endl; }
从缓冲区中取出一个产品的函数: //从缓冲区中取出一个产品 void Take() {
std::cerr << \"Taking a product *** \"; ConsumeID = g_buffer[out]; out = (out+1)%SIZE_OF_BUFFER; 利用信号量解决生产者消费者问题
6
std::cerr << \"Succeed\" << std::endl; }
(5)输出缓冲区当前的状态的函数: //输出缓冲区当前的状态 for (int i=0;i {
std::cout << i <<\": \" << g_buffer[i]; if (i==in)
std::cout << \" <-- 生产\"; if (i==out)
std::cout << \" <-- 消费\"; std::cout << std::endl; } }
(6)消耗一个产品的函数: //消耗一个产品 void Consume() {
std::cerr << \"Consuming \" << ConsumeID << \" *** \"; std::cerr << \"Succeed\" << std::endl; }
3.生产者和消费者算法 (1)生产者算法: //生产者
DWORD WINAPI Producer(LPVOID lpPara) {
while(g_continue) {
WaitForSingleObject(g_hFullSemaphore,INFINITE); WaitForSingleObject(g_hMutex,INFINITE); Produce(); Append(); Sleep(1500);
ReleaseMutex(g_hMutex);
ReleaseSemaphore(g_hEmptySemaphore,1,NULL);
7
}
return 0; }
(2)消费者算法: //消费者
DWORD WINAPI Consumer(LPVOID lpPara) {
while(g_continue) {
WaitForSingleObject(g_hEmptySemaphore,INFINITE); WaitForSingleObject(g_hMutex,INFINITE); Take(); Consume(); Sleep(1500);
ReleaseMutex(g_hMutex);
ReleaseSemaphore(g_hFullSemaphore,1,NULL); }
return 0; }
(4)运行结果分析
8
9
输入输出数据说明和分析:
该程序设置的缓冲区数据长度为20,生产者个数为3,消费者个数为1,程序启动后,生产者先进行生产,当3个生产者全部生产完之后,消费者开始从缓冲区中取出产品,当消费者取出一个后,生产者开始继续生产,当生产完3个之后,消费者开始从缓冲池中取产品,依次循环。 (5)设计小结
本次课程设计主要是对操作系统中线程,进程同步互斥等知识的应用,生产者—消费者问题是很著名的同步问题,的确是既简单又复杂。此次编写的程序基本实现了设计要求的功能,对于老师提出的关于程序的一点小修改仍未能解答出来,但是给我一点时间,我相信以后定能理解。
一个课程设计让我对本学期所学的操作系统课程有了更深刻的理解,加强了对基础的实践运用能力,让我能把所学的知识融会贯通。 (6)参考文献
【1】计算机操作系统(第3版),汤小丹,西安电子科技大学出版社. 【2】Visual C++面向对象编程教程,王育坚,清华大学出版社.
10
因篇幅问题不能全部显示,请点此查看更多更全内容