消费者生产者问题 计算机操作系统课程设计

更新时间:2024-04-29 16:37:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

齐齐哈尔大学

操作系统课程综合实践

题目:多进程同步方法解决生产

者-消费者问题

班级: 0

姓名: 0

学号: 0

指导教师: 0

2011年12 月7日

综合实践评分表

班级 0 姓名 0 指导教师 0 题目:多进程同步方法解决生产者-消费者问题

评分标准 评分标准 分数权重 10 评分的依据 A 选题符合大纲要求,题目较新颖,工作量大 态度端正,能主动认真完成各个环节的工作,不迟到早退,出勤好。 能正确选择存储结构,定义准确,算法流程图或类C语言描述的算法准确无误 具有独立分析、解决问题能力,有一定的创造性,能够独立完成软件的设计与调试工作,程序结构清晰,逻辑严谨,功能完善。 能准确回答老师提出的问题 程序运行正确、界面清晰,测试数据设计合理。 格式规范,层次清晰,设计思想明确,解决问题方法合理,体会深刻。 总分 C 选题基本符合大纲 要求,工作量适中 能够完成各环节基 本工作,出勤较好。 能正确选择存储结构,算法流程图或类 C语言描述的算法基本准确 有一定的分析、解决问题能力。能够在老师指导下完成软件 的设计与调试工作,程序功能较完善。 能基本准确回答老 师提出的问题 程序运行正确、界面较清晰,能给出合适 的测试数据。 格式较规范,设计思想基本明确,解决问 题方法较合理。 得分 选题 工作态度 10 存储结构、算法描述 20 独立解决问题的能力 10 答辨问题回答 程序运行情况 综合实践报告 20 10 20 指导教师(签字): 注:介于A和C之间为B级,低于C为D级和E级。按各项指标打分后,总分在90~100为优,80~89为良,70~79为中,60~69为及格,60分以下为不及格。

2

多进程同步方法解决生产者-消费者问题

摘要:本文论述了多进程同步方法解决生产者-消费者问题的过程。该程序使学生对操作系统的工作机制有了初步的了解,其主要目的是使学生了解和撑握在Linux系统平台下的C语言编程,用来解决实现生活中遇到的问题。并以Linux系统开发平台,以及虚拟机来实现。 关键字:生产者-消费者问题,Linux系统平台,虚拟机,信号量,线程(thread)

3

多进程同步方法解决生产者-消费者问题

一、课程设计所需设备

计算机一台,Red Hat linux9.03系统一套。

二、课程设计预期目的

通过研究Linux 的进程机制和信号量实现生产者消费者问题的并发控制。

三、课程设计任务

用多进程同步方法解决生产者-消费者问题

设计目的:通过研究Linux 的进程机制和信号量实现生产者消费者问题的并发控制.

说明:有界缓冲区内设有20个存储单元,放入/取出的数据项设定为1-20这20个整型数. 设计要求:

1) 每个生产者和消费者对有界缓冲区进行操作后,即时显示有界缓冲区的全部内容,当前指针位置和生产者/消费者线程的标识符. 2) 生产者和消费者各有两个以上.

3) 多个生产者或多个消费者之间须有共享对缓冲区进行操作的函数代码.

四、课程设计基本思想

多进程是一种非常简洁的多任务操作方式。在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种烦琐的多任务工作方式。

生产者-消费者方案是多进程应用程序开发中最常用的构造之一。因此困难也在于此。因为在一个应用程序中可以多次重复生产者-消费者行为,其代码也可以如此。设计中创建了 Consumer 类,该类通过在一些多进程应用程序中促进代码重用以及简化代码调试和维护来解决这个问题。多进程应用程

4

序通常利用生产者-消费者编程方案,其中由生产者进程创建重复性作业,将其传递给作业队列,然后由消费者进程处理作业。

多进程是一种使应用程序能同时处理多个操作的编程技术。通常有两种不同类型的多进程操作使用多个进程:适时事件,当作业必须在特定的时间或在特定的间隔内调度执行时;后台处理,当后台事件必须与当前执行流并行处理或执行时;适时事件的示例包括程序提醒、超时事件以及诸如轮询和刷新之类的重复性操作。后台处理的示例包括等待发送的包或等待处理的已接收的消息。

生产者-消费者方案很适合于后台处理类别的情况。这些情况通常围绕一个作业“生产者”方和一个作业“消费者”方。当然,关于作业并行执行还有其它考虑事项。在大多数情况下,对于使用同一资源的作业,应以FCFS的方式按顺序处理,这可以通过使用单进程的消费者轻松实现。通过使用这种方法,使用单个进程来访问单个资源,而不是用多个进程来访问单个资源。要启用标准消费者,当作业到来时创建一个作业队列来存储所有作业。生产者进程通过将新对象添加到消费者队列来交付这个要处理的新对象。然后消费者进程从队列取出每个对象,并依次处理。当队列为空时,消费者进入休眠。当新的对象添加到空队列时,消费者会醒来并处理该对象。

五.详细设计

5.1、调试问题分析

为解决生产者/消费者问题,应该设置两个资源信号量,其中一个表示空缓冲区的数目,用Full表示,其初始值为有界缓冲区的大小BUFFER_NUM;另一个表示缓冲区中产品的数目,用Empty表示,其初始值为0。另外,由于有界缓冲区是一个临界资源,必须互斥使用,所以还需要再设置一个互斥信号量Mutex,起初值为1。

在生产者/消费者问题中,信号量实现两种功能。首先,它是生产产品和消费产品的计数器,计数器的初始值是可利用的资源数目(有界缓冲区的长度)。其次,它是确保产品的生产者和消费者之间动作同步的同步器。 生产者要生产一个产品时,首先对资源信号量Full和互斥信号量Mutex进行P操作,申请资源。如果可以通过的话,就生产一个产品,并把产品送入缓冲区。然后对互斥信号量Mutex和资源信号量Empty进行V操作,释放资源。

消费者要消费一个产品时,首先对资源信号量Empty和互斥信号量Mutex进行P操作,申请资源。如果可以通过的话,就从缓冲区取出一个产品并消

5

费掉。然后对互斥信号量Mutex和资源信号量Full进行V操作,释放资源。

如果缓冲区中已经没有可用资源,就把申请资源的进程添加到等待队列的队尾。如果有一个资源被释放,在等待队列中的第一个进程被唤醒并取得这个资源的使用权。

5.2、程序流程图

生产者线程开始 未通过 资源信号量P操作 通过 互斥信号量P操作 通过 未通过 线程自我阻塞 生产一个产品 线程自我阻塞 把产品送入缓冲区 添加到等待队列 互斥信号量V操作 添加到等待队列 Y 等待队列中有消费者线程 N 资源信号量V操作 唤醒对头的消费者线程 Y 等待队列中有消费者线程 N 唤醒对头的消费者线程 生产者线程结束 图一 生产者流程结构

6

消费者线程开始 未通过 资源信号量P操作 通过 互斥信号量P操作 通过 未通过 线程自我阻塞 从缓冲区取出一个产品 线程自我阻塞 消费一个产品 添加到等待队列 互斥信号量V操作 添加到等待队列 Y 等待队列中有生产者线程 N 资源信号量V操作 唤醒对头的生产者线程 Y 等待队列中有生产者线程 N 唤醒对头的生产者线程 消费者线程结束

图二 消费者流程结构

5.3、程序自定义函数

1、void produce(struct sem_info * );

这个函数是生产者进行的生产过程,为所有的生产者所共享。结构体指针用来接收生产者线程创建时传来的生产者的个人信息。 2、void consumer(struct sem_info * );

这个函数是消费者进行的生产过程,为所有的消费者所共享。结构体指

7

针用来接收消费者线程创建时传来的消费者的个人信息。 3、void setproduce(void);

这个函数是用来设置生产者的个数和他们的名字。 4、void setconsumer(void);

这个函数是用来设置消费者的个数和他们的名字。 5、void activepthread(int);

这个函数是用来创建生产者线程,int型参数为生产者的个数。 6、void activecthread(int);

这个函数是用来创建生产者线程,int型参数为生产者的个数。 7、int gettime(void);

这个函数返回来一个整数,作为线程的sleep()函数的参数。 8、void myscanf(void);

这个函数用来获取设置生产者和消费者的个数时的整数,确保这个数字在0到MAX_BUFFER之间。

5.4、系统函数调用

线程

Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a。Linux下pthread的实现是通过系统调用clone()来实现的。clone()是Linux所特有的系统调用,它的使用方式类似fork。

函数pthread_create用来创建一个线程,它的原型为:

extern int pthread_create __P ((pthread_t *__thread, __const pthread_attr_t *__attr,

void *(*__start_routine) (void *), void *__arg));

第一个参数为指向线程标识符的指针,第二个参数用来设置线程属性,第三个参数是线程运行函数的起始地址,最后一个参数是运行函数的参数。第二个参数我们也设为空指针,这样将生成默认属性的线程。当创建线程成功时,函数返回0,若不为0则说明创建线程失败,常见的错误返回代码为EAGAIN和EINVAL。前者表示系统限制创建新的线程,例如线程数目过多了;后者表示第二个参数代表的线程属性值非法。创建线程成功后,新创建的线程则运行参数三和参数四确定的函数,原来的线程则继续运行下一行代码。 函数pthread_join用来等待一个线程的结束。函数原型为: extern

int

pthread_join

__P

((pthread_t

__th,

void

**__thread_return));

第一个参数为被等待的线程标识符,第二个参数为一个用户定义的指针,它

8

可以用来存储被等待线程的返回值。这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回。一个线程的结束有两种途径,一种是函数结束了,调用它的线程也就结束了;另一种方式是通过函数pthread_exit来实现。它的函数原型为:

extern void pthread_exit __P ((void *__retval)) __attribute__ ((__noreturn__));

唯一的参数是函数的返回代码,只要pthread_join中的第二个参数thread_return不是NULL,这个值将被传递给 thread_return。最后要说明的是,一个线程不能被多个线程等待,否则第一个接收到信号的线程成功返回,其余调用pthread_join的线程则返回错误代码ESRCH。 信号量

信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。当公共资源增加时,调用函数sem_post()增加信号量。只有当信号量值大于0时,才能使用公共资源,使用后,函数sem_wait()减少信号量。函数sem_trywait()和函数pthread_ mutex_trylock()起同样的作用,它是函数

sem_wait()的非阻塞版本。它们都在头文件

/usr/include/semaphore.h中定义。

信号量的数据类型为结构sem_t,它本质上是一个长整型的数。函数sem_init()用来初始化一个信号量。它的原型为:

extern int sem_init __P ((sem_t *__sem, int __pshared, unsigned int __value));

sem为指向信号量结构的一个指针;pshared不为0时此信号量在进程间共享,否则只能为当前进程的所有线程共享;value给出了信号量的初始值。 函数sem_post( sem_t *sem )用来增加信号量的值。当有线程阻塞在这个信号量上时,调用这个函数会使其中的一个线程不在阻塞,选择机制同样是由线程的调度策略决定的。

函数sem_wait( sem_t *sem )被用来阻塞当前线程直到信号量sem的值大于0,解除阻塞后将sem的值减一,表明公共资源经使用后减少。函数sem_trywait ( sem_t *sem )是函数sem_wait()的非阻塞版本,它直接将信号量sem的值减一。

函数sem_destroy(sem_t *sem)用来释放信号量sem。

六.源程序清单

9

6.1、源程序

#include #include #include #include

typedef HANDLE Semaphore; // 信号量的Windows原型 #define P(S) WaitForSingleObject(S,INFINITE) // 定义Windows下的P操作 #define V(S) ReleaseSemaphore(S,1,NULL) // 定义Windows下的V操作

#define rate 1000

#define CONSUMER_NUM 2 /* 消费者个数 */ #define PRODUCER_NUM 3 /* 生产者个数 */ #define BUFFER_NUM 20 /* 缓冲区个数 */

char *thing[8] = {\鸡腿堡\, \薯条\, \可乐\, \三明治\, \面包\, \小笼包\, \火腿\,\馒头\}; //生产和消费的产品名称 struct Buffer {

int product[BUFFER_NUM]; // 缓冲区

int start,end; // 两个指针相当于教材中的 in out 指针 }g_buf;

Semaphore Empty,Full,Mutex; //分别相当于Empty, Full, Mutex三个信号量

/************** 消费者线程*****************************/ DWORD WINAPI Consumer(LPVOID para) {

// i表示第i个消费者

int i = *(int *)para; //利用para传入当前消费者的编号 int ptr; // 待消费的内容的指针 printf(\消费者: 需要资源\\n\, i); int j=0;

while (j++<4){ // 等待产品 P(Full);

// 有产品,先锁住缓冲区 P(Mutex);

// 记录消费的物品 ptr=g_buf.start;

// 再移动缓冲区指针

g_buf.start= (g_buf.start+1)%BUFFER_NUM;

10

//让其他消费者或生产者使用

printf(\消费者d: 我需要buf[%d]=%s\\n\,i, ptr, thing[g_buf.product[ptr]]); //消费完毕,并释放一个缓冲

printf(\消费者d: 我消费完毕%s\\n\, i,thing[g_buf.product[ptr]]); V(Mutex); V(Empty);

Sleep(rate*rand()+110); }

return 0; }

/**************** 生产者线程

********************************/ DWORD WINAPI Producer(LPVOID para) {

int i = *(int *)para - CONSUMER_NUM; int ptr;

int data; //产品 int j=0;

while(j++<4) {

data=rand()%8;

printf(\生产者d: 生产出: %s!\\n\,i,thing[data]); //等待存放空间 P(Empty);

//有地方,先锁住缓冲区 P(Mutex);

//记录消费的物品 ptr=g_buf.end; //再移动缓冲区指针

g_buf.end =(g_buf.end+1)%BUFFER_NUM; printf(\生产者d: 放到缓冲区 buf[%d]=%s\\n\,i,ptr,thing[data]); g_buf.product[ptr]=data; //放好了完毕,释放一个产品 //让其他消费者或生产者使用 V(Mutex); V(Full);

Sleep(rate/2*rand()+110); }

return 0;

11

}

int main(int argc,char *argv[]) {

//线程技术,前面为消费者线程,后面为生产者线程

HANDLE hThread[CONSUMER_NUM+PRODUCER_NUM]; // 线程计数

srand(time(NULL)); rand();

DWORD tid; int i=0;

//初始化信号量

Mutex=CreateSemaphore(NULL,1,1,\); Empty=CreateSemaphore(NULL, BUFFER_NUM, BUFFER_NUM, \);

Full=CreateSemaphore(NULL,0,BUFFER_NUM,\); if(!Empty||!Full||!Mutex) {

printf(\);

return -1; }

int totalThreads=CONSUMER_NUM+PRODUCER_NUM; //开启消费者线程

printf(\先请消费者上席!\\n\); for(i=0;i

hThread[i]=CreateThread(NULL, 0, Consumer, &i,0,&tid); if(hThread[i])WaitForSingleObject(hThread[i],10); }

printf(\生产者就位!\\n\); for(;i

hThread[i]=CreateThread(NULL,0,Producer,&i,0,&tid); if(hThread[i])WaitForSingleObject(hThread[i],10); }

//生产者和消费者的执行

WaitForMultipleObjects(totalThreads,hThread,TRUE,INFINITE); return 0; }

12

6.2、编译及运行结果

在程序中设置了两个消费者,三个生产者,为便于描述出生产-消费的过程,我用食物代表被缓冲区消费的资源。在实验结果中我们可以看到几个生产者生产的食物放在缓冲区中,消费者需求的话就去缓冲区去取。在同一个时间点上不必生产者生产一个消费者就消费一个,消费者某个时间消费的资源有可能是上一个生产者所生产的。由于按题目要求设置的缓冲区为20,所以缓冲区没有溢出到等待消费者消费的情况。

图三 运行结果

七.课程设计总结

本次课程设计通过模拟计算机操作系统中经典的“生产者—消费者问题”,巩固了我在操作系统原理课上所学的知识,加深了对操作系统中进程同步和互斥等问题,完成了多进程同步方法解决生产者-消费者问题全部过程,结果满足设计要求。

设计过程中遇到不少困难,在反复研究老师的PPT及课本的原理后才逐

13

渐明晰怎样将代码实现,虽然这学期学过Java,但java不是很熟悉,因此还是选择C++语言。以前学习C++没有深入了解到线程这个概念,在学习Java的时候才知道有专门的线程类。所以我在网上也参考了其他人用C++编写尤其是关于多进程程序的设计实现。在代码的实现过程中,我是主要定义了两个函数 DWORD WINAPI Consumer(LPVOID para) 和 DWORD WINAPI Producer(LPVOID para),在这两个函数中实现PV操作。

通过本次设计,我较好地掌握了通过研究Linux 的进程机制和信号量实现生产者消费者问题的并发控制全过程,尤其是对多进程程序设计方法有了更深的理解,开拓了思路,锻炼了实践动手能手。但是我觉得课程设计的时间有点短,中间又夹杂着好几场考试,所以没能做出界面以便于直观的观察出详细过程,只是用代码实现了要描述的功能且达到了要求,所以改进的空间还比较大。

参考文献

[1]汤子瀛.《计算机操作系统》(修订版).西安电子科技大学出版社,2001。 [2]计算机操作系统教程.孙钟秀等编著.高等教育出版社,2010年8月出版 [3]数据结构教程.李春葆等编著 清华大学出版社.2009年4月

[4]面向对象程序设计与Visual C++6.0教程 陈天华编著 清华大学出版社.2009年7月

14

本文来源:https://www.bwwdw.com/article/orsg.html

Top