PV操作解决生产者消费者问题.docx
《PV操作解决生产者消费者问题.docx》由会员分享,可在线阅读,更多相关《PV操作解决生产者消费者问题.docx(15页珍藏版)》请在冰豆网上搜索。
PV操作解决生产者消费者问题
合肥学院
计算机科学与技术系
课程设计报告
2009~2010学年第1学期
课程名称
操作系统原理
课程设计名称
PV操作解决生产者——消费者问题
专业班级
07级计科1班
学生姓名
马峻、宣磊、施红陵、王锐
学生学号
0704011015/25/30/41
指导教师
屠菁
2009年12月
1、实验目的
进程是程序在一个数据集合上运行的过程,进程是并发执行的,也即系统中的多个进程轮流地占用处理器运行。
我们把若干个进程都能进行访问和修改的那些变量称为公共变量。
由于进程是并发地执行的,所以,如果对进程访问公共变量不加限制,那么就会产生“与时间有关”的错误,即进程执行后所得到的结果与访问公共变量的时间有关。
为了防止这类错误,系统必须要用同步机构来控制进程对公共变量的访问。
一般说,同步机构是由若干条原语——同步原语——所组成。
本实习要求模拟PV操作同步机构的实现,模拟进程的并发执行,了解进程并发执行时同步机构的作用。
2、实验内容
模拟实现用同步机构避免发生进程执行时可能出现的与时间有关的错误。
3、实验步骤
(1)任务分析
本实验要求利用PV操作实现解决生产者——消费者问题中的同步问题。
此问题描述的是一群生产者进程在生产产品并将这些产品提供给消费者进程去消费,在两者之间设置了一个具有n个缓冲区的缓冲池,生产者进程将它所生产的产品放入一个缓冲区,消费者进程可从缓冲区中取走产品去消费,但它们之间必须保持同步,即不允许消费者进程到一个空缓冲区去取产品,也不允许生产者进程向一个已装满且尚未取出的缓冲区中投放产品,并且生产者消费者互斥使用缓冲区。
本程序实现的功能是可以利用生产者进程进行生产,同时消费者进程也能进行消费,但是必须满足同步的条件才可以允许,否则将提示缓冲区满无法进行生产或者缓冲区空无法进行消费的错误,故程序应该具有判断的功能。
若结束当前的生产者——消费者进程,将会提示此次进程中生产消费者分别生产了和消费的产品数目,并统计缓冲区中剩余的产品数目,最后才结束。
(2)概要设计
本函数主要用到生产者函数RP-ProceducerThread(void*p)来实现缓冲区产品数量的增加,用RP-ConsumerThread(void*p)来实现缓冲区产品的减少。
并用到了CreateThread函数来创建生产者消费者线程,利用线程的句柄以及创建线程是立刻运行的特点来进行生产消费操作。
至于PV算法的实现是利用buffer_empty和buffer_full来进行控制,buffer_empty的值可以看做资源量,只有空的数值大于0才可以进行生产,buffer_full的数值与buffer_empty的值有对应的关系,利用buffer_full来控制消费的进行。
最后,在缓冲区操作临界资源PC_Buffer来说利用EnterCriticalSection(&PC_Buffer);//等待共享资源LeaveCriticalSection(&PC_Buffer);//释放共享资源来进行缓冲区操作的控制。
总之当程序读入测试文件中的数据时,便根据读入的字符时C还是P创造相应的进程,在分配EMPTY或者FULL资源,然后等待共享资源PC_Buffer,得到后便进行操作。
最后读入所有的数据,完成所有进程的操作。
(3)详细设计
1、做出如下定义CRITICAL_SECTIONPC_Buffer;//临界区
HANDLEh_semaphore_empty,h_semaphore_full;//信号量对象
structBuffer
{
charbuf[BUFFER_LEN];
}Buf[MAX_THREAD_NUM];
intin=0;//生产者生产指针
intout=0;//消费者消费指针
SYSTEMTIMEtimeSys;
CRITICAL_SECTIONPC_Buffer;//临界区
HANDLEh_semaphore_empty,h_semaphore_full;//信号量对象
structThreadInfo
{
intserial;//线程序号
charentity;//线程类别(P-生产者线程,C-消费者线程)
doubledelay;//生产产品/延续时间
doublepersist;//线程把产品加入到缓冲区/持续时间
};
2①CreateThread函数创建一个在调用进程的地址空间中执行的线程。
此函数为API函数,用于创建生产者消费者进程。
②信号量对象(semaphore)
信号量对象实现了Dijkstra定义中的通用信号量语义。
信号量对象就是资源信号量,初始值的取值在0到指定最大值之间,用于限制并发访问的线程数,也可用于进程、线程间的同步。
它的相关API包括:
CreateSemaphore、OpenSemaphore和ReleaseSemaphore。
(1)CreateSemapore函数是创建一个有名或者无名信号量对象,在输人参数中指定最大值和初值,返回对象句柄。
格式:
HANDLECreateSemaphore(LPSECURITY_ATTRIBUTESlpAttributes,
LONGlInitialCount,LONGlMaximumCount,LPCTSTRlpName);
1pAttributes:
安全属性。
如果是NULL就表示要使用默认属性。
1InitialCount:
Semaphore的初值。
必须≥0,并且≤MaximumCount。
lMaximumCount:
Semaphore的最大值。
这也就是在同一时间内能够锁住Semaphore之线程的最多个数。
1pName:
Semaphore的名称(一个字符串)。
任何线程(或进程)都可以根据这一名称引用到这个Semaphore。
这个值可以是NULL,意思是产生一个没有名字的Semaphore。
返回值:
如果成功就传回一个handle,否则传回NULL
③等待操作
Windows2000为对象提供了两个统一的等待操作函数WaitForSingleObject和WaitForMultipleObjiects,等待函数被同步对象用于实现各种Dijkstra定义的P操作。
等待的对象包括:
Changenotification(改变通告);Consoleinput(控制台输入);Event(事件);Job(作业);Mutex(互斥对象);Process(进程);Semaphore(信号量);Thread(线程);Waitabletimer(可等待定时器)。
函数决定等待条件是否被满足。
如果等待条件并没有被满足,调用线程进入一个高效的等待状态,当等待满足条件时占用非常少的处理器时间。
在运行前,一个等待函数修改同步对象类型的状态。
修改仅发生在引起函数返回的对象身上。
例如,信号的计数减1。
一个线程通过调用等待函数拥有对象。
创建该对象的线程也拥有对象,而不需要调用等待函数。
(1)WaitForSingleObject函数可在指定的时间内等待指定对象为可用状态
当下列情况之一发生时该函数返回:
(1)指定对象处于信号态;
(2)超时。
格式:
DWORDWaitForSingleObject(HANDLEhHandle,DWORDdwMilliseconds);
hHandle:
等待对象句柄。
dwMilliseconds:
指定以毫秒为单位的超时间隔。
如果超时,即使对象的状态是非信号态的并且没有完成,函数也返回。
如果它是0,函数测试对象的状态并立刻返回;如果它是INFINITE(定义为0xFFFFFFFF或-1),函数从不超时。
返回值:
如果函数调用成功,返回值表明引起函数返回的事件。
可能值如下:
WAIT_ABANDONED:
指定对象是互斥对象,在线程被终止前,线程没有释放互斥对象。
互斥对象的所属关系被授予调用线程,并且该互斥对象被置为非信号态。
WAITOBJECT_0:
指定对象的状态被置为信号态。
WAIT_TIMEOUT:
超时,并且对象的状态为非信号态。
如果函数调用失败,返回值是WAIT_FAILED。
(2)WaitForMultipleObjects函数可在指定的时间内等待多个对象为可用状态。
格式:
DWORDWaitForMultipleObjects(DWORDnCount,CONSTHANDLE*lpHandles,
BOOLbWaitAll,DWORDdwMilliseconds);
nCount规定了可引起函数阻塞的一组对象的句柄数目。
lpHandles指向存放一组句柄的数组。
bWaitAll规定了是否函数应该等待一组对象都发送出有信号通知(bWaitAll=TRUE),或者只是等待一个对象(bWaitAll=FLASE)。
dwMilliseconds,它同在WaitForSingleObiect一样。
④临界区对象
临界区对象只能用于在同一个进程内使用的临界区,同一个进程内各线程对它的访问是互斥进行的,临界区对象的运行速度比互斥对象快。
把变量说明为CRITICAL_SECTION类型,就可作临界区使用。
相关的API包括InitializeCriticalSection、EnterCriticalSection、TryEnterCriticalSection、LeaveCriticalSection和DeleteCriticalSection。
(1)InitializeCriticalSection函数初始化临界区对象。
格式:
VOID InitializeCriticalSection(LPCRITICAL_SECTIONlpCriticalSection);
lpCriticalSection:
指向临界区对象的指针。
(2)EnterCriticalSection函数是等待指定临界区对象的所有权。
当调用线程被赋予所有权时,该函数返回。
格式:
VOID EnterCriticalSection(LPCRITICAL_SECTIONlpCriticalSection);
lpCriticalSection:
指向临界区对象的指针。
(3)LeaveCriticalSection函数释放指定临界区对象的所有权
格式:
VOIDLeaveCriticalSection(LPCRITICAL_SECTIONlpCriticalSection);
1pCriticalSection:
指向临界区对象的指针。
(4)DeleteCriticalSection释放与临界区对象相关的所有系统资源。
(5)临界区对象的应用例
进程通过声明CRITICAL_SECTION类型的变量来完成分配临界区对象使用的存储空间,使用InitializeCriticalSection来初始化该临界区对象。
线程在进入临界区之前使用EnterCriticalSection等待占用临界区的使用权,线程在退出临界区之后马上使用LeaveCriticalSection释放临界区的使用权。
利用临界区对象实现一个进程的各线程互斥的程序如下:
……
CRITICAL_SECTIONPC_Buffer;
structThreadInfo
{……};
voidRP_ProceducerThread(void*p)
{
……
EnterCriticalSection(&PC_Buffer);//等待共享资源
……(临界区)//生产者把产品加入到缓冲区
LeaveCriticalSection(&PC_Buffer);//释放共享资源
……
}
intmain(intargc,char*argv[])
{
HANDLEh_Thread;
DWORD thread_ID;
ThreadInfothread_info[MAX_THREAD_NUM];
InitializeCriticalSection(&PC_Buffer);
……
h_Thread=CreateThread(NULL,0,
(LPTHREAD_START_ROUTINE)(RP_ProceducerThread),
&thread_info[i],0,&thread_ID);
……
}
⑤生产者消费者函数
voidRP_ProceducerThread(void*p)
{
DWORDm_delay;//生产产品延续时间
DWORDm_persist;//把产品加入到缓冲区持续时间
intm_serial;//线程序号
DWORDwait_for_semaphore_empty;//等待资源信号量所有权
longcount;
//从参数中获得信息
m_serial=((ThreadInfo*)(p))->serial;
m_delay=(DWORD)(((ThreadInfo*)(p))->delay*INTE_PER_SEC);
m_persist=(DWORD)(((ThreadInfo*)(p))->persist*INTE_PER_SEC);
Sleep(m_delay);//生产产品
printtime("Producer","requests",m_serial);
wait_for_semaphore_empty=WaitForSingleObject(h_semaphore_empty,-1);
//等待资源信号量empty
EnterCriticalSection(&PC_Buffer);//等待共享资源
printtime("Producer","begins",m_serial);
Sleep(m_persist);//生产者把产品加入到缓冲区
printtime("Producer","finishs",m_serial);
LeaveCriticalSection(&PC_Buffer);//释放共享资源
ReleaseSemaphore(h_semaphore_full,1,&count);//释放资源信号量full
}
voidRP_ConsumerThread(void*p)
{
DWORDm_delay;//消费产品持续时间
DWORDm_persist;//从缓冲区取出产品持续时间
intm_serial;//消费线程序号
DWORDwait_for_semaphore_full;//等待资源信号量
longcount;
//从参数中获得信息
m_serial=((ThreadInfo*)(p))->serial;
m_delay=(DWORD)(((ThreadInfo*)(p))->delay*INTE_PER_SEC);
m_persist=(DWORD)(((ThreadInfo*)(p))->persist*INTE_PER_SEC);
Sleep(m_delay);
printtime("Consumer","requests",m_serial);
wait_for_semaphore_full=WaitForSingleObject(h_semaphore_full,-1);
//等待资源信号量full
EnterCriticalSection(&PC_Buffer);//等待共享资源
printtime("Consumer","begins",m_serial);
Sleep(m_persist);//消费者从缓冲区取出产品
printtime("Consumer","finishs",m_serial);
LeaveCriticalSection(&PC_Buffer);//释放共享资源
ReleaseSemaphore(h_semaphore_empty,1,&count);//释放资源信号量empty
Sleep(m_delay);//消费者消费产品
}
3.其它API函数
(1)Sleep函数对于指定的时间间隔挂起当前的执行线程。
格式:
VOIDSleep(DWORDdwMilliseconds);
dwMilliseconds:
定义挂起执行线程的时间,以毫秒(ms)为单位。
取值为0时,该线程将余下的时间片交给处于就绪状态的同一优先级的其他线程。
若没有处于就绪状态的同一优先级的其他线程,则函数立即返回,该线程继续执行。
若取值为INFINITE则造成无限延迟。
返回值:
该函数没有返回值。
(2)GetLocalTime函数取得当前的本地时间和日期。
格式:
BOOLGetLocalTime(lpst);
lpst:
指向一个SYSTEMTIME结构,该结构存放当前的本地时间和日期。
typedefstruct_SYSTEMTIME{
WORDwYear; WORDwMonth; WORDwDayOfWeek; WORDwDay;
WORDwHour; WORDwMinute;WORDwSecond; WORDwMilliseconds;
}SYSTEMTIME;
(4)调试分析
a、该程序总体上来看比较简单,在调试过程中只要注意一些小问题,则不会出现问题了,除了少许语法错误外,控制好程序的走向,则逻辑错误亦可避免;
程序的总体思路很清晰,最主要的就是要实现两个条件判断,即缓冲区满的时候不允许生产者进行生产,若缓冲区空的话,则不进行消费者进行消费;在生产操作和消费操作之间进行相应的判断,正好符合PV信号量,先做P操作,若满足,则执行此进程,若不满足,则阻塞此进程,并做相应的V操作,即唤醒其对应的进程,从而很好的解决了生产者——消费者问题;
b、该程序通过信号量的EMPTY和信号量FULL的控制来执行wait_for_semaphore_empty=WaitForSingleObject(h_semaphore_empty,-1);
//等待资源信号量empty
wait_for_semaphore_full=WaitForSingleObject(h_semaphore_full,-1);
//等待资源信号量full
利用EnterCriticalSection(&PC_Buffer);来实现线程进入共享资源的运行从而实现
生产者-消费者问题的缓冲区操作限制:
1)生产者-消费者互斥使用缓冲区;
2)生产者-消费者同步之一:
生产者线程先把产品加入到缓冲区,消费者线程才能从缓冲区取出产品。
3)生产者-消费者同步之二:
消费者线程从缓冲区取出产品后,生产者线程才能把产品再加入到缓冲区。
(5)测试结果
(6)使用说明
在测试数据文件(PCdata.txt)的例子中输入数据,例如:
1C43
2C43
3P44
4P44
5P44
6C43
编译程序,运行程序,可得到数据的输出。
4、实验总结
本实验要求模拟PV操作同步机构,来解决生产者——消费者问题,所以我们首先应该弄清楚的一个问题就是何为生产者——消费者问题?
说的简单点就是:
在一个规定大小的缓冲区中,生产者可将生产好的产品投入进去,消费者亦可从中取出产品进行消费,但是有两件事是不允许发生的,即不允许消费者从一个空的缓冲区取产品,也不可让生产者向一个满的缓冲区投放产品,故在做相应的生产消费操作时就需要进行判断,到底能不能做此操作,这便是同步机制,通过设置一对信号量,来对其操作进行合法性判断和控制,达到两个过程可以准确无误的执行。
整个程序只需要一个子函数即可完成上述所有的功能,根据测试数据文件(PCdata.txt)中第一列的字符做相应的操坐,若是P,则是生产操作,生产者生产一个产品并送入缓冲区;若是C,则是消费操作,消费者从缓冲区取出一个产品进行消费;当进程全部运行结束后,则结束次程序。
通过这个实验,可以帮助我们很好的理解进程同步的问题,要解决进程同步需要做哪些工作,如何利用信号量机制来解决进程同步问题等等,这些问题其实我们在学习理论知识时都是很少思考的,因为感触不深,所以学了一遍就过去了,但是在自己做实验时才会发现哪些地方是我们需要考虑的,哪些地方是需要注意的,实验给了我们实践的机会,给了我们理论结合实际的机会,从实验中可以学到很多东西,不仅仅是书本上的东西这么简单,更重要的是对待事情严谨的态度,对待任何事情都要一丝不苟,细节决定成败!
5、附录(源程序及实验运行结果截图)
#include
#definebuffer10
intn=buffer;//空的缓冲区数
intproduce=0;
intconsume=0;
charYesorNot=0,input;
voidospv()
{
cout<<"请进行生产/消费操作!
"<while
(1)
{
cin>>input;
if((input=='a'||input=='A')&&(n>0))
{
produce++,n--;
cout<<"生产一个产品并已送到缓冲区."<continue;
}
if((input=='B'||input=='b')&&(n{
consume++,n++;
cout<<"从缓冲区取走了一个产品并进行消费."<continue;
}
if(input=='c'||input=='C')
break;
if(n<=0)
{
cout<<"对不起!
缓冲区已满!
不能进行生产!
"<continue;
}
if(n=buffer)
{
cout<<"对不起!
缓冲区已空!
不能进行消费!
"<continue;
}
}
cout<<"是否结束生产/消费操作(Y/N)"<cin>>YesorNot;
if(YesorNot=='y'||YesorNot=='Y')
{
cout<<"此次生产/消费操作结束."<cout<<"产品生产总数为:
"<cout<<"产品消费总数为:
"<cout<<"缓冲区剩余产品数为:
"<}
if(YesorNot=='n'||YesorNot=='N')
ospv();
}
voidmain()
{
cout<<"键入A/a:
生产一个产品并送到缓冲区."<cout<<"键入B/b:
从缓冲区取走一个产品并进行消费."<cout<<"键入C/c:
生产/消费过程结束."<ospv();
}
运行结果: