当前位置:文档之家› 实验一 进程同步与互斥

实验一 进程同步与互斥

实验一进程同步与互斥一、实验目的1.掌握基本的同步与互斥算法,理解生产者消费者模型。

2.学习使用Windows 2000/XP中基本的同步对象,掌握相关API的使用方法。

3.了解Windows 2000/XP中多线程的并发执行机制,实现进程的同步与互斥。

二、实验内容及要求1.实验内容以生产者/消费者模型为依据,在Windows 2000环境下创建一个控制台进程,在该进程中创建n个线程模拟生产者和消费者,实现进程(线程)的同步与互斥。

2.实验要求, 学习并理解生产者/消费者模型及其同步/互斥规则;, 学习了解Windows同步对象及其特性;, 熟悉实验环境,掌握相关API的使用方法;, 设计程序,实现生产者/消费者进程(线程)的同步与互斥;, 提交实验报告。

三、相关知识介绍1.同步对象同步对象是指Windows中用于实现同步与互斥的实体,包括信号量(Semaphore)、互斥量(Mutex)、临界区(Critical Section)和事件(Events)等。

本实验中使用到信号量、互斥量和临界区三个同步对象。

同步对象的使用步骤:, 创建/初始化同步对象。

, 请求同步对象,进入临界区(互斥量上锁)。

, 释放同步对象(互斥量解锁)。

这些对象在一个线程中创建,在其他线程中都可以使用,实现同步与互斥。

2.相关API的功能及使用我们利用Windows SDK提供的API编程实现实验题目要求,而VC中包含有Windows SDK的所有工具和定义。

要使用这些API,需要包含堆这些函数进行说明的SDK头文件——最常见的是Windows.h(特殊的API调用还需要包含其他头文件)。

下面给出的是本实验使用到的API的功能和使用方法简单介绍。

(1) CreateThread, 功能——创建一个在调用进程的地址空间中执行的线程, 格式HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes,DWORD dwStackSize,LPTHREAD_START_ROUTINE lpStartAddress,LPVOID lpParamiter,DWORD dwCreationFlags,Lpdword lpThread );, 参数说明lpThreadAttributes——指向一个LPSECURITY_ATTRIBUTES(新线程的安全性描述符)。

dwStackSize——定义原始堆栈大小。

lpStartAddress——指向使用LPTHRAED_START_ROUTINE类型定义的函数。

lpParamiter——定义一个给进程传递参数的指针。

dwCreationFlags——定义控制线程创建的附加标志。

lpThread——保存线程标志符(32位)(2) CreateMutex, 功能——创建一个命名或匿名的互斥量对象 , 格式HANDLE CreateMutex(LPSECURITY_ATTRIBUTES lpMutexAttributes,BOOL bInitialOwner,LPCTSTR lpName);, 参数说明lpMutexAttributes——必须取值NULL。

bInitialOwner——指示当前线程是否马上拥有该互斥量(即马上加锁)。

lpName——互斥量名称。

(3) CreateSemaphore, 功能——创建一个命名或匿名的信号量对象 , 格式HANDLE CreateSemaphore(LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, LONG lInitialCount,LONG lMaximumCount,LPCTSTR lpName );, 参数说明lpSemaphoreAttributes——必须取值NULL。

lInitialCount——信号量的初始值。

该值大于0,但小于lMaximumCount指定的最大值。

lMaximumCount——信号量的最大值。

lpName——信号量名称。

(4) WaitForSingleObject, 功能——使程序处于等待状态,直到信号量hHandle出现(即其值大于等于1)或超过规定的等待时间, 格式DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds);, 参数说明hHandle——信号量指针。

dwMilliseconds——等待的最长时间(INFINITE为无限等待)。

(5) ReleaseSemaphore, 功能——对指定信号量加上一个指定大小的量。

成功执行则返回非0值, 格式BOOL ReleaseSemaphore(HANDLE hSemaphore,LONG lReleaseCount,LPLONG lppreviousCount );, 参数说明hSemaphore——信号量指针。

lReleaseCount——信号量的增量。

lppreviousCount——保存信号量当前值。

(6) ReleaseMutex, 功能——打开互斥锁,即把互斥量加1。

成功调用则返回0, 格式BOOL ReleaseMutex(HANDLE hMutex);, 参数说明hMutex——互斥量指针。

(7) InitializeCriticalSection, 功能——初始化临界区对象, 格式VOID InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection); , 参数说明lpCriticalSection——指向临界区对象的指针。

(8) EnterCriticalSection , 功能——等待指定临界区对象的所有权 , 格式VOID enterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);, 参数说明lpCriticalSection——指向临界区对象的指针。

(9) LeaveCriticalSection , 功能——释放指定临界区对象的所有权 , 格式VOID LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection);, 参数说明lpCriticalSection——指向临界区对象的指针。

四、实验示例(方法、步骤与例程)1.测试用例文件测试用例文件用于描述各线程的有关信息,该文件内容及格式如下:31 P 32 P 43 C4 14 P 25 C 3 1 2 4说明:第一行给出的是程序中设置的临界区个数;其余各行是各进程信息。

每行中的数据之间用Tab键分隔。

第一列(除第一行外):线程号。

第二列:P——生产者,C——消费者。

第三列:线程在生产和消费前的休眠时间,单位为秒。

第四及以后各列:消费的产品所对应的生产者线程号。

2.数据结构(1) 用整型数组Buffer_Critical表示缓冲区。

(2) 用自定义结构ThreadInfo记录一条线程信息,多个线程对应一个ThreadInfo数组。

(3) 通过如下同步对象实现互斥:, 设一个互斥量h-mutex,实现生产者在查询和保留缓冲区的下一个空位置时进行互斥。

, 设置h_Semaphore[MAX_THREAD_NUM]信号量数组表示相应产品已经生产,实现生产者与消费者之间的同步。

同时,用表示空缓冲区树木的信号量empty_semephore指示是否存在空位置,实现类似的同步,以便开始下一个产品的生产。

, 设置临界区对象数组PC_Critical[MAX_BUFFER_NUM]实现每个缓冲区上消费者之间的互斥。

3.程序结构为了方便,程序结构用如下的文字予以描述。

(1) 主函数(2) 初始化缓冲区、消费请求队列及部分同步对象(3) 提取线程信息(4) 完成线程相关同步对象的初始化(5) 创建线程,模拟生产者和消费者(6) 等待所有线程结束(7) 程序结束(8) 消费者(9) 有无消费请求?有,则继续(10);无,则转(16)(10) 此请求可满足?可满足,转(11);否,则阻塞,再转(10)(11) 确定产品位置(12) 此产品正被消费?是,则阻塞,再转(12);否,则转(13)(13) 进入临界区(请求同一产品的消费者之间互斥)(14) 消费产品,并判断是否应该释放产品所占缓冲区(15) 退出临界区,转(9)(16) 结束消费者线程(17) 生产者(18) 存在空缓冲区?有,则继续(19);无,则阻塞,再转(18)(19) 另一生产者在写?否,则转(20);是,则阻塞,再转(19)(20) 进入临界区(请求同一产品的生产者之间互斥)(21) 在缓冲区中为本线程产品分配空间(22) 退出临界区(23) 写入产品到分配的缓冲区空间中(24) 结束生产者线程4.示例程序//**************************R_WP1.CPP******************************* //#include<windows.h>#include<fstream.h>#include<stdio.h>#include<string>#include<conio.h>//本程序允许的最大临界区数#define MAX_BUFFER_NUM 10//秒到微秒的乘法因子#define INTE_PER_SEC 1000//生产和消费进程总数#define MAX_THREAD_NUM 64//定义记录在测试文件中的线程参数数据结构struct ThreadInfo{int serial;char entity;double delay;int thread_request[MAX_THREAD_NUM];int n_request;};//全局变量定义//临界区对象的声明,用于管理缓冲区的互斥访问 CRITICAL_SECTION PC_Critical[MAX_BUFFER_NUM];int Buffer_ Critical[MAX_BUFFER_NUM]; //缓冲区声明 HANDLEh_Thread[MAX_THREAD_NUM]; //存储线程句柄的数组ThreadInfoThread_info[MAX_THREAD_NUM]; //线程信息数组 HANDLEempty_semephore; //信号量 HANDLE h_mutex; //互斥量 DWORD n_Thread=0; //实际线程数目 DWORD n_Buffer_or_Critical; //实际缓冲区/临界区数目HANDLE h_semephore[MAX_THREAD_NUM]; //生产者允许消费的信号量//生产、消费及辅助函数的声明void Produce(void *p);void Consume(void *p);bool IfInOtherRequest(int);int FindProducePosition();int FindBufferPosition(int);int main(void){DWORD wait_for_all;ifstream inFile;//初始化缓冲区for(int i=0;i<MAX_BUFFER_NUM;i++)Buffer_Critical[i]=-1;//初始化各线程的请求队列for(int j=0;j<MAX_THREAD_NUM;j++){ for(int k=0;j<MAX_THREAD_NUM;k++)Threa_Info[j].thread_request[k]=1;Thread_Info[j].n_request=0;}//初始化临界区对象for(int i=0;i<MAX_BUFFER_NUM;i++)InitializeCriticalSection(&PC_Critical[i]);//打开输入文件,提取线程信息inFile.open(“test.txt”);//从文件中获得实际缓冲区数目inFile>>n_Buffer_or_Critical;inFile.get();printf(“输入文件是:\n”);//回显获得的缓冲区数目printf(“%d \n”,(int)n_Buffer_or_Critical);//提取各线程信息到相应的数据结构中while(inFile){ inFile>>Thread_Info[n_Thread].serial;inFile>>Thread_Info[n_Thread].entity;inFile>>Thread_Info[n_Thread].delay;char c;inFile.get(c);while(c!=’\n’&&!inFile.eof()){ inFile>>Thread_Info[n_Thread].thread_request[Thread_Info[n_Thread].n_request ++];inFile.get(c);}n_Thread++;}//回显获得的线程信息,便于确认正确性for(j=0;j<(int)n_Thread;j++){int Temp_serial=Thread_Info[j].serial;char Temp_entity=Thread_Info[j].entity;double Temp_delay=Thread_Info[j].delay;printf(“ \n thread%2d %c %f”,Temp_serial,Temp_entity,Temp_delay);int Temp_request=Thread_Info[j].n_request;for(int k=0;k<Temp_request;k++)printf(“ %d ”,Thread_In[j].thread_request[k]);cout<<endl;}printf(“\n\n”);//创建模拟过程中必要的信号量empty_semaphore=CreateSemaphore(NULL,n_Buffer_or_Critical,n_Buffer_or_Critical, “semephore_for_empty”);h_mutex=CreateMutex(NULL,FALSE, “mutex_for_update”);//用线程ID为产品读写时所使用的同步信号量命名for(j=0;j<(int)n_Thread;j++){std::string lp=”semephore_for_produce”;int temp=j;while(temp){char c=(char)(temp%10);lp+=c;temp/=10;}h_Semaphore[j+1]=CreateSemaphore(NULL,0,n_Thread,lp.c_str());}//创建生产者和消费者线程for(i=0;i<(int)n_Thread;i++){if(Thread_Info[i].entity==’P’)h_Thread[i]=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)(Produce),&( Thread_Info[i]),0,NULL);h_Thread[i]=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)(Consume), &(Thread_Info[i]),0,NULL);}//主程序等待各个线程的动作结束wait_for_all=WaitForMultipleObjects(n_Thread,h_Thread,TRUE,-1);printf(“ \n \nALL Producer and Consumer have finished their work, \n”);printf(“Press any key to quit!\n”); return 0;}//确认是否还有对同一产品的消费请求未执行bool IfInOtherRequest(int req){for(int i=0;i<n_Thread;i++)for(int j=0;j<Thread_Info[i].n_request;j++)if(Thread_Info[i].thread_request[j]==req)return TRUE;return FALSE;}//找出当前可以进行产品生产的空缓冲区位置int FindProducePosition(){int EmptyPosition;for(int i=0;i<n_Buffer_or_Critical;i++)if(Buffer_Critical[i]=-1){EmptyPosition=i;//用下列特殊值表示本缓冲区正处于被写状态Buffer_Critical[i]=-2;Break;}return EmptyPosition;}//找出当前需要生产者生产的产品的位置 int FindBufferPosition(int ProPos){int TempPos;for(int i=0;i<n_Buffer_or_Critical;i++)if(Buffer_Critical[i]=ProPos){TempPos=i;Break;}return TempPos;}//生产者线程void Produce(void *p){//局部变量声明DWORD wait_for_semaphore,wait_for_mutex,m_delay;int m_serial;//获得本线程信息m_serial=((ThreadInfo*)(p))->serial;m_delay=(DWORD)(((ThreadInfo*)(p))->delay *INTE_PER_SEC);Sleep(m_delay);//开始请求生产printf(“Producer %2d sends the produce require.\n”,m_serial);//确认有空缓冲区,同时把空位置数empty减1,用于生产/消费同步wait_for_semaphore=WaitForSingleObject(empty_semaphore,-1);//互斥访问下一个用于生产的空临界区,实现写写互斥wait_for_mutex= WaitForSingleObject(h_mutex,-1);int ProducePos=FindProducePosition();ReleaseMutex(h_mutex);//生产者在获得的空位置做标记,以下的写操作在生产者之间可以并发执行//在核心生产步骤中,程序用生产者的ID作为产品编号供消费者识别printf(“Producer %2d begin to produce atposition %2d.\n”,m_seri al,ProducePos);Buffer_Critical[ProducePos]=m_serial;printf(“Producer %2d finish producing :\n”,m_serial);printf(“Position[ %2d ]:%3d\n”,ProducePos,Buffer_Critical[ProducePos]);//实现读写同步ReleaseSemaphore(h_Semaphore[m_serial],n_Thread,NULL);}//消费者线程void Consume(void *p){//局部变量声明DWORD wait_for_semaphore,m_delay;int m_serial,m_requestNum; //消费者序列号和请求的数目int m_thread_request[MAX_THREAD_NUM]//本消费线程的请求队列//提取本线程的信息m_serial=((ThreadInfo*)(p))->serial;m_delay=(DWORD)(((ThreadInfo*)(p))->delay *INTE_PER_SEC); m_requestNum=((ThreadInfo*))->n_request;for(int i=0;i<m_requestNum;i++)m_thread_request[i]=((ThreadInfo*)(p))->thread_request[i]; Sleep(m_delay);//循环进行所需产品的生产for(i=0;i<m_requestNum;i++){//请求消费下一个产品printf(“ Consumer %2d re quest to consume %2dproduct\n”,m_serial,m_thread_request[i]);//若生产者未生产,则等待;//若生产了,允许消费数目为-1;实现读写同步wait_for_semaphore=WaitForSingleObject(h_semaphore[m_thread_request[ i]],-1)//查询所需产品在缓冲区的号int BufferPos=FindBufferPosition(m_thread_request[i]);//开始消费,读读互斥//进入临界区消费,消费完毕通知其他消费者自己的请求已满足//若产品消费完,应做相应处理,并给出提示//相应处理是指把相应的缓冲区清空,并增加代表空缓冲区的信号量EnterCriticalSection(&PC_Critical[BufferPos]);Printf(“Consumer%2d begin to cosume %2dproduct\n”,m_serial,m_thread_request[i]);((ThreadInfo*)(p))->thread_request[i]=-1;if(!IfInOtherRequest(m_thread_request[i])){Buffer_Critical[BufferPos]=-1; //标记缓冲区为空printf(“Consumer%2d finish consuming %2d:\n”,m_serial,m_thread_request[i]);printf(“position[%2d]:%3d\n”,BufferPos,Buffer_Critical[BufferPos]);ReleaseSemaphore(empty_semaphore,1,NULL);}else{printf(“Consumer %2d finish consuming product %2d\n”,m_serial,m_thread_request[i]);}//离开临界区LeaveCriticalSection(&PC_Critical[BufferPos]); }}。

相关主题