1.1 实验目标
掌握操作系统对进程管理的同步和互斥问题,以经典同步问题“生产者和消费者”为例,运用所学C语言知识,编写该问题的模拟运行环境。
1.2 实验环境
教学机房,C语言程序平台。
1.3 实验内容及步骤
1.了解经典同步问题“生产者和消费者”
生产者与消费者可以通过一个环形缓冲池联系起来,环形缓冲池由几个大小相等的缓冲块组成,每个缓冲块容纳一个产品。每个生产者可不断地每次往缓冲池中送一个生产产品,而每个消费者则可不断地每次从缓冲池中取出一个产品。指针i和指针j分别指出当前的第一个空缓冲块和第一个满缓冲块。
2.分析和理解
(1)既存在合作同步问题,也存在临界区互斥问题
合作同步:当缓冲池全满时,表示供过于求,生产者必须等待,同时唤醒消费者;当缓冲池全空时,表示供不应求,消费者应等待,同时唤醒生产者。
互斥:缓冲池显然是临界资源,所在生产者与消费都要使用它,而且都要改变它的状态。
(2)基于环形缓冲区的生产者与消费者关系形式描述:
公用信号量mutex:初值为1,用于实现临界区互斥
生产者私用信号量empty:初值为n,指示空缓冲块数目
消费者私用信号量full:初值为0,指示满缓冲块数目
整型量i和j初值为0,i指示首空缓冲块序号,j指示首满缓冲块序号
(3)PV原语
var mutex,empty,full:semaphore;
i,j:integer;buffer:array[0...n-1] of item;
i:=j:=1;
Procedure producer;
begin
while true do
begin
produce a product;
P(empty);
P(mutex);
buffer(i):=product;
i:=(i+1) mod n;
V(mutex);
V(full);
end;
end;
Procedure consumer;
begin
P(full);
P(mutex);
goods:=buffer(j);
j:=(j+1) mod n;
V(mutex);
V(empty);
consume a product;
end;
end;
3.用C语言编程搭建“生产者和消费者”经典进程通信问题的环境。要求程序运行时,按任意键停止,显示当前系统的各个参数的值。提交实验报告,以及相关程序列表。打包成附件上传。
#include
#include
#include
#define P(S) WaitForSingleObject(S,INFINITE)//定义Windows下的P操作
#define V(S) ReleaseSemaphore(S,1,NULL)//定义Windows下的V操作
#define rate 1000
#define CONSUMER_NUM 10 /*消费者个数*/
#define PRODUCER_NUM 10 /*生产者个数*/
#define BUFFER_NUM 4 /*缓冲区个数*/
typedef HANDLE Semaphore; //信号量的Windows原型
char *thing[10]= {"物品1","物品2","物品3","物品4","物品5",
"物品6","物品7","物品8","物品9","物品10"};
struct Buffer
{
int product[BUFFER_NUM]; //缓冲区
int start,end; //两个指针
}g_buf; Semaphore g_semBuffer,g_semProduct,g_mutex;
//消费者线程
DWORD WINAPI Consumer(LPVOID para)
{
int i=*(int *)para; //i表示第i个消费者
int ptr,j; //待消费的内容的指针
Sleep(100);
while(1)
{
P(g_semProduct); //有产品,先锁住缓冲区
P(g_mutex); //记录消费的物品
ptr=g_buf.start; //再移动缓冲区指针
g_buf.start=(g_buf.start+1)%BUFFER_NUM;
V(g_mutex); //让其他消费者或生产者使用 g_buf
printf("消费者%d:消费了buf[%d]里的=%s\n",i,ptr,thing[g_buf.product[ptr]]);
Sleep(rate*rand()%10+110);
//消费完毕,并释放一个缓冲
V(g_semBuffer);
if(j++>30)break;
}
getchar();
return 0;
}
// 生产者线程
DWORD WINAPI Producer(LPVOID para)
{
int i=*(int *)para-CONSUMER_NUM;
int ptr;
int data; //产品
Sleep(100);
while(1)
{
Sleep(rate*rand()%10+110);
data=rand()%10;
//等待存放空间
P(g_semBuffer); //有地方,先锁住缓冲区
P(g_mutex);
// 记录消费的物品
ptr=g_buf.end;
// 再移动缓冲区指针
g_buf.end=(g_buf.end+1)%BUFFER_NUM;
//让其他消费者或生产者使用 g_buf
V(g_mutex);
printf("生产者%d:在buf[%d]里放入了%s\n",i,ptr,thing[data]);
g_buf.product[ptr]=data;
Sleep(rate/2*rand()%10+110);
//放好了完毕,释放一个产品
V(g_semProduct);
}
return 0;
}
int main(int argc,char *argv[])
{ //线程技术,前面为消费者线程,后面为生产者线程
HANDLE hThread[CONSUMER_NUM+PRODUCER_NUM]; //线程计数
//srand(time());
DWORD tid;
int i=0;
// 初始化信号量
g_mutex=CreateSemaphore(NULL,BUFFER_NUM,BUFFER_NUM,"mutexOfConsumerAndProducer");
g_semBuffer=CreateSemaphore(NULL,BUFFER_NUM,BUFFER_NUM,"BufferSemaphone");
g_semProduct=CreateSemaphore(NULL,0,BUFFER_NUM,"ProductSemaphone");
if(!g_semBuffer||!g_semProduct||!g_mutex)
{
printf("Create Semaphone Error!\n");
return -1;
}
int totalThreads=CONSUMER_NUM+PRODUCER_NUM;
// 开启消费者线程
for(i=0;i { hThread[i]=CreateThread(NULL,0,Consumer,&i,0,&tid); if(hThread[i])WaitForSingleObject(hThread[i],10); } //开启 生产者线程 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; } 1.思考在“生产者和消费者”经典同步问题中,两个P操作是否可以互换位置,以及两个V操作是否可以互换位置。 在生产者-消费者问题中,如果将两个P操作,即P(full)和P(mutex)互换位置,或者P(empty)和P(mutex)互换位置,都可能引起死锁。考虑系统中缓冲区全满时,若以生产者进程先执行了P(mutex)操作并获得成功,当再执行P(empty)操作时,他将因失败而进入阻塞状态,它期待消费者执行V(empty)来唤醒自己。在此之前,它不可能执行V(mutex)操作,从而使企图通过P(mutex)进入自己的临界区的其他生产者和所有消费者进程全部进入阻塞状态,从而引起系统死锁。类似地,消费者进程若先执行P(mutex),后执行P(full),同样可能造成死锁。 V(full)和V(mutex)互换位置,或者V(empty)和V(mutex)互换位置,则不会引起死锁,其影响只是使临界资源的释放略微推迟一些。 2.思考在“哲学家就餐”经典同步问题中,如何修改程序,可以保证不会发生死锁现象。 (1)至多只允许有四位哲学家同时去拿左边的筷子,最终能保证至少有一位哲学家能够进餐,并在用毕是能释放出他用过的两只筷子,从而使更多的哲学家能够进餐。 (2)仅当哲学家的左、右两只筷子均可使用时,才允许他拿起筷子进餐。 (3)规定奇数号哲学家先拿他左边的筷子,然后再去拿右边的筷子,而偶数号哲学家则相反。按此规定,将是1、2号哲学家竞争1号筷子;3、4号哲学家竞争3号筷子。即五位哲学家都先竞争奇数号筷子,获得后,再去竞争偶数号筷子,最后总会有一位哲学家能获得两只筷子而进餐。 3.思考在“读者与写者”经典同步问题中,如何修改程序,变为“写者优先”的算法。 (写者优先算法) Var rmutex,wmutex,mutex,s:semaphore=1,1,1,1; Writecount:integer:=0; Reader:begin Repeat Wait(s); Wait(rmutex); If readcount=0 then wait(wmutex); Readcount:readcount+1; Signal(rmutex); Signal(s); Perform read operation; Wait(rmutex); Readcount:=readcount:=readcount-1; If readcount=0 then signal(wmutex); Signal(rmutex); Until false; End Writer:begin Repeat Wait(mutex); If writecount=0 then wait(s); Writecount:writecount+1; Signal(mutex); Wait(wmutex); Perform write operatin; Signal(wmutex); Wait(mutex); Writecount=0 then signal(s); Signal(mutex); Until false; end 4.分析以下进程运行环境中出现的同步和互斥现象,列出相应的变量和参数。 理发店理有一位理发师、一把理发椅和n把供等候理发的顾客坐的椅子。如果没有顾客,理发师便在理发椅上睡觉。一个顾客到来时,它必须叫醒理发师。如果理发师正在理发时又有顾客来到,则如果有空椅子可坐,就坐下来等待,否则就离开。 理发师是顾客争用资源,用信号量barbers表示,初值为0;除此之外,顾客还要争用n张椅子,信号量customers表示等候理发的顾客,初值为0;最后设置信号量mutex用于这两个活动对资源barbers/customers的互斥,初值为1.另外还需要使用一个变量waiter,用于记录等候的顾客的数量。 实验二 进程的互斥与同步(生产者与消费者问题)实验报告 实验目的:利用Windows提供的API函数,用VISUALC++ 6.0编写程序,解决生产者与消费者问题,实现进程的互斥与同步 实验内容和步骤:本实验设计在同一个进程地址空间内执行的七个线程。三个生产者线程四个消费者线程。生产者线程生产物品,然后将物品放置在一个空缓冲区中供消费者线程消费。消费者线程从缓冲区中获得物品,然后释放缓冲区。生产者线程生产物品时,若无空缓冲区可用,生产者线程必须等待消费者线程释放出一个空缓冲区;消费者线程消费物品时,若无满的缓冲区,消费者线程将被阻塞,直到新的物品被生产出来。 本实验中三个生产者线程的区别只在生产产品的时间不同,分别为:1秒、2秒、3秒;四个消费者线程的区别也只在消费产品的时间不同,分别为:0.5秒、1秒、1.5秒、2秒。它们使用二个不同的缓冲区。需要使用如下信号量: 一个互斥信号量,用以阻止生产者线程和消费者线程同时操作缓冲区列表; 一个信号量,用于当缓冲区满时让生产者线程等待; 一个信号量,用于当缓冲区空时让消费者线程等待; 接下来利用VC++6.0编写一段程序,模拟生产者和消费者线程,实现进程的互斥与同步。程序结果通过DOS下窗口输出,使生产者和消费者自动生产和消费产品,通过按回车键来使程序退出。 主程序结构: 1、 此程序要用到的头文件,有3个:windows.h、iostream、d_random.h(用于产生随机数模拟产品) 2、 全局变量的定义,分别有:h_Mutex(互斥信号)、bufferFullSemaphore(当缓冲区满时生产者等待信号量)、bufferEmptySemaphore(当缓冲区空时消费者等待信号量)、BUFFER_SIZE(缓冲区长度)、buffer[](数组模拟缓冲区循环队列)、in(用与追踪产品进缓冲区时的缓冲区数组下标)、out(用与追踪产品出缓冲区时的缓冲区数组下标)、rndNum(用于产生随机数)、producerID[](生产者线程的标识符)、consumerID[](消费者线程的标识符)、control(控制生产者消费者线程的循环的标志) 3、 生产者函数,3个生产者的函数名字分别为:producer1、producer2、producer3,其中区别只在于函数中的Sleep()的参数不同,分别为:1000、2000、3000。此函数用于执行生产者线程时所要进行的操作,本程序模拟生产者生产一个10000到99999的随机数做产品,并输出生产出的这个数字,并输出产品生产后缓冲区的储存情况 4、 消费者函数,4个消费者的函数名字分别为:consumer1、consumer2、consumer3、consumer4,其中区别只在于函数中的Sleep()的参数不同,分别为:500、1000、1500、2000。此函数用于执行消费者线程时所要进行的操作,本程序模拟消费者消费一个缓冲区已经存在的数字,消费完后使此缓冲区的数字变0来表示该区无产品,并输出产品生产后缓冲区的储存情况 5、 主函数部分,对全局变量定义的信号量赋值,创建3个生产者线程和4个消费者线程,实验当按回车键的时候程序退出(使控制变量control为false) 实验中所遇到的问题及解决办法: 1、 生产者消费者函数问题。 由于3个生产者函数之间和4个消费者函数之间的区别只是Sleep()这个函数的参 数不同,于是一开始我编了一个生产者函数和一个消费者函数,它们都接受一个参数sleepTime用于确定Sleep()函数中的参数。但在创建线程的函数CreateThread()中,生产者函数和消费者函数要做为这个函数的第三个参数传进来,而这个创建线程的函数的参数 定义来看,第三个传递进来的函数不能接受非void *类型的参数,于是该方案失败,考虑过把生产者和消费者创建成类,但还是失败了,最终解决办法是编写了3个生产者函数和4个消费者函数,导致代码加长。更有效的解决办法还请老师指导指导。 2、 程序结束问题。 一开始我想让程序自动结束,初步想法是定义一个变量追踪实际线程数目,再定义一 个常量为最大线程数目,当实际线程数目大于最大线程数目时程序结束(见我原程序注释掉了的变量及程序),此办法我运行后程序并不能如我想的结束,百思不得其解后我变把程序改成用回车键来结束了1.4 实验思考题
第二篇:实验二 生产者与消费者实验报告