操作系统实验 生产者消费者问题详解

article/2025/11/10 0:46:16

操作系统课程设计 生产者消费者实验报告

一、实验目的

加深对进程概念的理解,明确进程与程序的区别。
认识并发执行的本质。
理解和掌握Linux和Windows进程通信系统调用的功能,通过实验和学习,提高对进程痛惜系统调用的编程能力。
掌握使用信号量机制完成进程间同步和互斥的方法。

二、实验内容

设置一个大小为的缓冲区,且初始为空。
创建两个生产者和三个消费者。
生产者和消费者都必须用进程模拟。
对于生产者而言,随机等待一段时间后,往缓冲区中存数据,若缓冲区满,则等待消费者取走数据后再存放。
对于消费者而言,随机等待一段时间后,从缓冲区中取数据,若缓冲区空,则等待生产者存放数据后再取走。
每个生产者进程需要执行6次存数据的操作,每个消费者进程需要执行4次取数据的操作。
显示每次向缓冲区添加或取走的数据以及时间,每次操作后都要显示缓冲区中的全部数据,生产者、消费者进程号。

三、实验环境

虚拟机: Ubuntu 18.04
Linux操作系统计:Linux-5.9.10
操作系统:Windows10
Win10下的IDE:Visual Studio2019

四、程序设计与实现

4.1 设计思路

定义大小为3的共享内存区:
struct BUF {
char array[BufLength];
int head;
int tail;
int IsEmpty;};
其中array表示生产者和消费者存取数据的环形缓冲区,设置其大小BufLength为3。头指针head用于指向消费者下一次待取的数据,尾指针tail指向生产者下一次需要存放数据的缓冲,IsEmpty用来表示环形缓冲区是否为空,取0为不空,取1为空。
在实验中,设置三个信号量如下:
互斥信号量MUTEX:用于生产者进程与生产者进程、消费者进程与生产者进程、消费者进程与消费者进程间互斥使用缓冲区,初始值为1。
同步信号量EMPTY:用于指示当前空缓冲区的可用数量,用于制约生产者进程向缓冲区存数据,初始值为3。
同步信号量FULL:用于指示当前有数据的缓冲区的数量,用于制约消费者进程取数据,初始值为0。
假设P(MUTEX)操作是申请MUTEX资源,V(MUTEX)操作是释放MUTEX资源,给出生产者消费者问题的流程如下。

生产者存数据的流程:

(1) 生产数据
(2) P(EMPTY) //消费者进程申请一个空缓冲单元,EMPTY-1
(3) P(MUTEX) //若申请到空缓冲单元,则申请这个缓冲单元的使用权,保证
//这段时间内没有其他进程在使用环形缓冲区,MUTEX-1
(4) 生产者进程向缓冲区中存放数据
(5) 修改尾指针,指向下一个缓冲单元(此缓冲单元不一定为空缓冲),修改缓冲区状态IsEmpty。
(6) V(MUTEX) //生产者进程释放对环形缓冲区的使用权,MUTEX+1
(7) V(FULL) //FULL+1,表示环形缓冲区中可取数据的总量,唤醒消费者进程
//取数据

消费者取数据的流程:

(1) P(FULL) //申请一个放有数据的缓冲单元(即申请一个产品),FULL-1
(2) P(MUTEX) //若申请到产品,保证这段时间没有其他进程在使用环形缓冲
//区,MUTEX-1
(3)取得当前头指针指向的数据,清除存储这一数据的缓冲单元的内容,修改缓冲区状态,并修改尾指针指向下一缓冲单元。
(4) V(MUTEX) //消费者进程释放对环形缓冲区的使用权
(5) V(EMPTY) //Empty+1,表示环形缓冲区中空缓冲单元的数量,用于唤醒生
//产者进程向缓冲区中存数据。
由于在生产与消费的过程中,有同步信号量FULL和EMPTY控制各进程(消费者或生产者)是否能够申请缓冲单元,所以不会出现缓冲区满而生产者仍然往里放数据,缓冲区空而消费者仍然在取数据的情况。此外,由于MUTEX的存在,保证各进程能够互斥使用环形缓冲区,MUTEX、FULL与EMPTY的合理搭配保证不会有死锁的产生。

4.2 Windows下的消费者生产者问题(详细见代码)

函数功能:

(1) 创建信号量:

HANDLE CreateSemaphore(  //创建信号量lpSemaphoreAttributes, //NULL表示默认属性lInitialCount,         //信号量的初值lMaximumCount,  //信号量的最大值lpName);         //信号量的名称

(2)释放信号量:

BOOL ReleaseSemaphore(hSemaphore,   //信号量的句柄lReleaseCount,     //信号量计数增加值lpPreviousCount);  //返回信号量原来值

(3)打开信号量:

HANDLE  OpenSemaphore(dwDesiredAccess,  bInheritHandle,  lpName)

(4)关闭信号量(hSemphore为信号量的句柄):

CloseHandle(hSemphore)

(5)创建共享内存区:

HANDLE CreateFileMapping(HANDLE hFile,// 文件句柄,填写 INVALID_HANDLE_VALUELPSECURITY_ATTRIBUTES lpFileMappingAttributes,//安全描述符 DWORD flProtect,    // 映射对象保护属性DWORD dwMaximumSizeHigh,		// 文件映射的最大长度的高32位DWORD dwMaximumSizeLow,		// 文件映射的最大长度的低32位LPCTSTR lpName);         // 文件映射对象名称

(6)将共享内存区映射到进程虚拟地址:

LPVOID MapViewOfFile(HANDLE hFileMappingObject,  //文件映像对象句柄DWORD dwDesiredAccess,      // 数据的访问方式DWORD dwFileOffsetHigh,     // 文件映射起始偏移的高32位DWORD dwFileOffsetLow,      // 文件映射起始偏移的低32位DWORD dwNumberOfBytesToMap);  // 文件中要映射的字节数,0表示映射整个文件映射对象

(7)打开进程中对应的内存映射对象(共享内存区):

HANDLE OpenFileMapping(DWORD dwDesiredAccess,  // 数据的访问方式BOOL bInheritHandle,    // 是否继承句柄LPCTSTR lpName );         // 要打开的文件映射对象名称

(8)取消进程地址空间的映射,关闭共享内存区:

UnmapViewOfFile(pLocalMem);  
pLocalMem=NULL;   
//关闭内核映射文件,hFileMapping 为内存映射文件句柄  
CloseHandle(hFileMapping);

父进程的流程:

(1) 创建信号量,创建共享内存区。使用CreateSemaphore()接口创建信号量MUTEX、FULL、EMPTY,并赋予初值分别为1、0、3,使用CreateFileMapping()接口创建共享内存区,内存区大小即为BUF的大小,并将共享内存区映射到父进程地址空间,得到共享内存区的入口地址pfile。
(2) 通过pfile对共享内存区进行初始化,在之后的各进程中,几乎都采用如下的方式对共享内存区进行读写操作。

if (pfile != NULL) {ZeroMemory(pfile, sizeof(struct BUF));
}//得到共享内存区的数据结构
struct BUF* pbuf = reinterpret_cast<struct BUF*>(pfile);
//初始化
pbuf->head = 0;
pbuf->tail = 0;
pbuf->IsEmpty = 0;
memset(pbuf->array, 0, sizeof(pbuf->array));

(3)调用UnmapViewOfFile(pfile)接口解除共享内存在父进程地址空间的映射,使用CloseHandle(pfile)关闭共享内存区
(4)创建三个生产者进程,两个消费者进程。在创建进程时使用了自己写的StartClone(int i)函数,详见代码。该函数在每个进程的argv[]参数中依次存放文件名、给子进程的编号(编号0、1、2表示该进程为生产者进程,编号3、4表示该进程为消费者进程)。
(5)等待所有子进程运行完毕,此处使用到WaitForMultipleObjects()接口
(6)调用CLoseHandle()接口关闭信号量

生产者进程流程:

(1) 调用OpenSemaphore()接口打开MUTEX、FULL、EMPTY信号量。
(2) 调用OpenFileMapping()接口打开共享内存区,调用MapViewOfFile()接口把该内存区映射到生产者进程的虚拟地址空间。
(3) 语句struct BUF* pbuf = reinterpret_cast<struct BUF*>(pfile)将pbuf指向共享内存区,且其结构为BUF,这使得之后在读取共享内存区中的数据时,可以照BUF的结构进行读取。
(4) 每个生产者进程进行6次存数据操作,存数据对应于4.1中的生产者存数据流程,只是增加了少许步骤。
生产者进程先通过GetRandomChar()得到数据,在申请空缓冲单元后,调用Sleep(GetRandomSleep())函数使其随机等待一段时间,在得到环形缓冲区的使用权后,存放数据。这两个函数为自己编写的函数,具体见代码。
在做完存数据的一系列操作之后,调用localtime()函数获取系统时间,并以时分秒的形式打印,除此,在每次存数据后,应打印出该该进程标识、存放的数据、环形缓冲区的所有数据以供观察,并在打印完后清空输出缓冲区。
(5) 调用UnmapViewOfFile()解除共享内存区在该进程地址空间的映射,并调用CloseHandle()关闭共享内存区句柄,关闭MUTEX、EMPTY、FULL信号量句柄。

消费者进程流程:

(1) 调用OpenSemaphore()接口打开MUTEX、FULL、EMPTY信号量。
(2) 调用OpenFileMapping()接口打开共享内存区,调用MapViewOfFile()接口把该内存区映射到生产者进程的虚拟地址空间,并得到pfile,可将其看做共享内存区的入口。
(3) 语句struct BUF* pbuf = reinterpret_cast<struct BUF*>(pfile)将pbuf指向共享内存区,且其结构为BUF,这使得之后在读取共享内存区中的数据时,可以照BUF的结构进行读取。
(4) 每个消费者进程进行4次取数据操作,取数据操作对应于4.1中消费者取数据流程,但步骤有所增加:
消费者进程在申请产品后调用Sleep(GetRaondomSleep())函数等待随机时间,在得到环形缓冲区的使用权后,后开始取数据,在完成取数据的一系列操作后,调用localtime()函数得到当前系统时间,并以时分秒的形式打印,除此,还需打印出消费者进程标识、取到的数据、当前环形缓冲区的全部数据等,并在打印完后清除输出缓冲区。
(5) 调用UnmapViewOfFile()解除共享内存区在该进程地址空间的映射,并调用CloseHandle()关闭共享内存句柄,关闭MUTEX、EMPTY、FULL信号量句柄。
在main()函数中,根据传过来的argv[1]参数(即我们给子进程的编号,令此编号为id)进行不同的操作,具体如下:

if argc>1:int id = atoi(argv[1]);if id < 0:exit(-1);//编号小于0说明出现了问题else if id < 2:生产者进程流程else if id <5:消费者进程流程
else:父进程流程

4.3 Linux下的生产者消费者问题(详见代码):

在Linux下,定义存取数据所需要的P、V操作如下:

void P(int sem_id, int sem_num){struct sembuf xx;xx.sem_num = sem_num;xx.sem_op = -1;xx.sem_flg = 0;semop(sem_id, &xx, 1);}
void V(int sem_id, int sem_num){struct sembuf xx;xx.sem_num = sem_num;xx.sem_op = 1;xx.sem_flg = 0;semop(sem_id, &xx, 1);}

在Linux下,共享内存区的定义与之前一致,只是将名字BUF改为了ShrBuf。

使用到的函数:

(1) 创建信号量或取得信号量

int semget(key_t key, int num_sems, int sem_flags);

参数key是整数值(唯一非零),不相关的进程可以通过它访问一个信号量,它代表程序可能要使用的某个资源,程序对所有信号量的访问都是间接的,程序先通过调用semget()函数并提供一个键,再由系统生成一个相应的信号标识符(semget()函数的返回值),只有semget()函数才直接使用信号量键,所有其他的信号量函数使用由semget()函数返回的信号量标识符。如果多个程序使用相同的key值,key将负责协调工作。
参数num_sems指定需要的信号量数目。
参数sem_flags是一组标志,当想要当信号量不存在时创建一个新的信号量,可以和值IPC_CREAT做按位或操作。设置了IPC_CREAT标志后,即使给出的键是一个已有信号量的键,也不会产生错误。而IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误。
semget()函数成功返回一个相应信号标识符(非零),失败返回-1。
(2)修改信号量的值

int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);

(3) 对信号量执行控制操作。如读取或修改信号量集合的有关状态信息,撤销信号量集合等等。

int semctl(int sem_id,int sem_num,int cmd,union semun arg);

参数分别为信号量集合标识、信号量索引、要执行的命令、设置信号量信息的参数。
(4) 申请(创建)一个共享内存区

shmget(key,size,shmflg)

key为共享内存区的关键字,shmflg为创建或打开标志,size是共享内存区字节长度。
(5) 将共享内存区加到申请通信的进程地址空间

shmat(shmid,shmadd,shmflg)

shmid是共享内存区的标识,shmadd是给出的附加到进程虚空间的地址,通常 shmflg和shmadd为0。
(6) 解除共享内存区与进程之间的连接

shmdt(shamaddr)

shmaddr是共享内存区在进程地址空间的虚地址
(7) 对共享内存区执行控制操作

int shmctl(int shmid,int cmd,struct shmid_ds *buf)

cmd参数可以指定以下值:
IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值。
IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值
IPC_RMID:删除共享内存段
SHM_LOCK:将共享段锁在内存
SHM_UNLOCK:解锁共享段

父进程流程:

(1) 调用semget()接口创建大小为3的信号量集,并返回信号量集的标识,若创建失败,应返回错误信息。
(2) 调用semctl()接口初始化信号量集中各信号的信息。在信号集中,索引0表示MUTEX,1表示EMPTY,2表示FULL。
(3) 调用shmget()接口申请共享内存区,且大小为ShrBuf的大小,若申请失败,应返回错误信息,若申请成功,调用shmat()接口将该共享内存区附加到父进程地址空间,结构体指针MyBuf指向这一地址空间,大致语句如下:

shm_id = shmget(IPC_PRIVATE,sizeof(ShrBuf),0600);
MyBuf = shmat(shm_id, 0, 0);

如此,父进程中便可借助MyBuf存取共享内存区,MyBuf是ShrBuf结构体类型的。子进程附加共享内存区的方式以及让MyBuf指向这一内存区的方式与父进程相同。
(4) 借助MyBuf指针初始化共享内存区
(5) 创建两个生产者进程,三个消费者进程,并让其做相关操作。
(6) 调用wait()接口等待子进程运行结束,因为创建了5个子进程,所有用while(wait(0)!=-1)语句来保证所有子进程都运行结束。
(7) 调用shmdt()接口解除父进程与共享内存区之间的连接,调用shmctl()接口释放共享内存区(指定cmd为IPC_RMID),调用shmctl()接口删除信号量集合(指定cmd为IPC_RMID)。

生产者进程流程:

(1) 调用shmat()接口将共享内存区映射到该进程的地址空间,若映射出错,返会错误信息,若成功映射则执行(2)
(2) 生产者进程要进行6次数据存储,每次存数据的流程与4.1中生产者存数据的流程大体一致,但是增加了几个步骤,如下:生产者进程在申请空缓冲单元和环形缓冲区的使用权后,调用sleep(GetRandomWait())函数使其随机等待一段时间,后将GetRandomInput()得到的随机数据存入缓冲单元。这两个函数为自己编写的函数,具体见代码。
在做完存数据的一系列操作之后,调用localtime()函数获取系统时间,并以时分秒的形式打印,除此,在每次存数据后,应打印出该该进程标识、存放的数据、环形缓冲区的所有数据以供观察,并在打印完后清空输出缓冲区。
(3) 在6次存储数据后,调用shmdt(MyBuf)接口取消共享内存区与生产者进程的连接。
(4) 调用exit(0)接口结束进程。

消费者进程流程:

(1) 调用shmat()接口将共享内存区映射到该进程的地址空间,若映射出错,返会错误信息,若成功映射则执行(2)
(2) 消费者进程要进行4次取数据操作,每次取数据的流程与4.1中消费者取数据的流程大体一致,但是增加了几个步骤,如下:
消费者进程在申请空缓冲单元和环形缓冲区的使用权后,调用sleep(GetRandomWait())函数使其随机等待一段时间。
在做完取数据的一系列操作之后,调用localtime()函数获取系统时间,并以时分秒的形式打印,除此,在每次存数据后,应打印出该该进程标识、存放的数据、环形缓冲区的所有数据以供观察,并在打印完后清空输出缓冲区。
(3) 在4次取数据后,调用shmdt(MyBuf)接口取消共享内存区与生产者进程的连接。
(4) 调用exit(0)接口结束进程。
在程序中,为了更好地区分父进程、消费者进程、生产者进程的执行关系,给出以下控制流:

main(){信号量的创建、初始化;共享内存的创建,共享内存区附加到父进程地址空间及初始化;for(i=0;i<2;i++):创建生产者进程:如果创建失败,返回错误信息;如果进程标识号为0:生产者进程流程;for(i=0;i<3;i++):创建消费者进程:如果创建失败,返回错误信息;如果进程标识号为0:消费者进程流程;等待子进程结束;解除映射、删除共享内存区、删除信号量集;
}

4.4 实验结果

4.4.1 Linux下的实验结果

在这里插入图片描述

查看共享内存、信号量是否被释放:
在这里插入图片描述

前后执行多次发现,结果都如上图,可见,在程序执行完后,信号量数组为空,这足以说明共享内存的和信号量在使用完后被释放掉。
4.4.2 Windows下的实验结果
在这里插入图片描述

五、实验收获与体会

本次实验主要是针对操作系统中的进程间同步和互斥等知识的运用。
在实验中,掌握了Linux和Windows下共享内存的创建、使用和销毁,掌握了信号量的申请、使用记忆销毁,对Linux和Windows下关于共享内存、信号量创建、使用、销毁以及进程创建、销毁的系统调用有了更深刻的认识和理解。
在实验中,掌握了如何通过信号量实现进程间的同步和互斥。
在实验中,要注意释放已申请的共享内存和信号量。在Linux下,需要调用shmctl来销毁共享内存区,调用semctl来销毁信号量;在Windows下,应记得调用CloseHandle()关闭句柄。
Windows下用WaitForSingleObject()来实现信号量的P操作。

六、实验代码

Windows下的实验代码

#include<windows.h>
#include<iostream>
#include<string>
#include<time.h>
#define BufLength 3
#define P(S) WaitForSingleObject(S,INFINITE);
#define V(S) ReleaseSemaphore(S,1,NULL);
static LPCTSTR filemapping_name = "FileMapping";
HANDLE ProcessHandle[5];
using namespace std;struct BUF {char array[BufLength];int head;int tail;int IsEmpty;
};int GetRandomSleep() {return (rand() + GetCurrentProcessId())%100 + 1000;
}char GetRandomChar() {return ((rand() + GetCurrentProcessId()) % 26 + 'A');
}///创建子进程 
void StartClone(const int id) {TCHAR szFilename[MAX_PATH];GetModuleFileName(NULL, szFilename, MAX_PATH);TCHAR szcmdLine[MAX_PATH];sprintf(szcmdLine, "\"%s\" %d", szFilename, id);STARTUPINFO si;ZeroMemory(reinterpret_cast<void*>(&si), sizeof(si));si.cb = sizeof(si);PROCESS_INFORMATION pi;BOOL bCreateOK = CreateProcess(szFilename,szcmdLine,NULL,NULL,FALSE,CREATE_DEFAULT_ERROR_MODE,NULL,NULL,&si,&pi);//通过返回的hProcess来关闭进程if (bCreateOK) {CloseHandle(pi.hProcess);CloseHandle(pi.hThread);ProcessHandle[id] = pi.hProcess;}else {printf("child process error!\n");exit(0);}
}///父进程程序 
void ParentProc() {//创建信号量 HANDLE MUTEX = CreateSemaphore(NULL, 1, 1, "mymutex");HANDLE EMPTY = CreateSemaphore(NULL, 3, 3, "myempty");HANDLE FULL = CreateSemaphore(NULL, 0, 3, "myfull");//创建内存映射HANDLE hMapping = CreateFileMapping(NULL, NULL, PAGE_READWRITE, 0, sizeof(struct BUF), filemapping_name);if (hMapping != INVALID_HANDLE_VALUE) {LPVOID pfile = MapViewOfFile(hMapping, FILE_MAP_ALL_ACCESS, 0, 0, 0);if (pfile != NULL) {ZeroMemory(pfile, sizeof(struct BUF));}//初始化struct BUF* pbuf = reinterpret_cast<struct BUF*>(pfile);pbuf->head = 0;pbuf->tail = 0;pbuf->IsEmpty = 0;memset(pbuf->array, 0, sizeof(pbuf->array));//解除映射UnmapViewOfFile(pfile);pfile = NULL;CloseHandle(pfile);}//生产者for (int i = 0; i < 2; i++) {StartClone(i);}//消费者for (int i = 2; i < 5; i++) {StartClone(i);}WaitForMultipleObjects(5, ProcessHandle, TRUE, INFINITE);//回收信号量 CloseHandle(EMPTY);CloseHandle(FULL);CloseHandle(MUTEX);
}
///生产者 
void Producer() {//打开信号量//HANDLE MUTEX = CreateMutex(NULL,FALSE,"mymutex");HANDLE MUTEX = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, "mymutex");HANDLE EMPTY = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, "myempty");HANDLE FULL = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, "myfull");//打开共享内存区,并加载到当前进程地址空间HANDLE hmap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, filemapping_name);LPVOID pfile = MapViewOfFile(hmap,FILE_MAP_ALL_ACCESS,0,0,0);struct BUF* pbuf = reinterpret_cast<struct BUF*>(pfile);for (int i = 0; i < 6; i++) {char ch = GetRandomChar();P(EMPTY);//sleepSleep(GetRandomSleep());P(MUTEX);pbuf->array[pbuf->tail] = ch;//存数据 pbuf->tail = (pbuf->tail + 1) % BufLength;//修改指针 pbuf->IsEmpty = 1;//修改状态 time_t t=time(NULL);struct tm* ptm = localtime(&t);//GetSystemTime(&tm);printf("\nProducerID:%6d, puts data:%c\tbuffer:\t%c\t%c\t%c\t%d时%d分%d秒",(int)GetCurrentProcessId(),ch,pbuf->array[0], pbuf->array[1], pbuf->array[2],ptm->tm_hour,ptm->tm_min,ptm->tm_sec);fflush(stdout);V(MUTEX);//释放缓冲区使用权 V(FULL);}//解除映射UnmapViewOfFile(pfile);pfile = NULL;CloseHandle(pfile);//关闭信号量CloseHandle(MUTEX);CloseHandle(EMPTY);CloseHandle(FULL);
}
///消费者 
void Customer() {//打开信号量HANDLE MUTEX = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, "mymutex");//HANDLE MUTEX = CreateMutex(NULL,FALSE,"mymutex");HANDLE EMPTY = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, "myempty");HANDLE FULL = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, "myfull");//打开共享内存区,并加载到当前进程地址空间HANDLE hmap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, filemapping_name);LPVOID pfile = MapViewOfFile(hmap,FILE_MAP_ALL_ACCESS,0,0,0);struct BUF* pbuf = reinterpret_cast<struct BUF*>(pfile);//读数据(取产品) for (int i = 0; i < 6; i++) {P(FULL);//sleepSleep(GetRandomSleep());P(MUTEX);char ch = pbuf->array[pbuf->head];pbuf->array[pbuf->head] = ' ';//将缓冲置空 pbuf->head = (pbuf->head + 1) % BufLength;//修改指针 pbuf->IsEmpty = (pbuf->head == pbuf->tail);//修改状态 time_t t=time(NULL);struct tm* ptm = localtime(&t);printf("\nCustomerID:%6d, gets data:%c\tbuffer:\t%c\t%c\t%c\t%d时%d分%d秒",(int)GetCurrentProcessId(),ch,pbuf->array[0], pbuf->array[1], pbuf->array[2],ptm->tm_hour,ptm->tm_min,ptm->tm_sec);fflush(stdout);V(EMPTY);//ReleaseMutex(MUTEX);V(MUTEX);//释放缓冲区使用权 }//解除映射UnmapViewOfFile(pfile);pfile = NULL;CloseHandle(pfile);//关闭信号量CloseHandle(MUTEX);CloseHandle(EMPTY);CloseHandle(FULL);}int main(int argc, char* argv[]) {if (argc > 1) {int id = atoi(argv[1]);if (id < 0) {printf("maybe child process error!\n");exit(-1);}else if (id < 2) {Producer();}else if (id < 5) {Customer();}}else {ParentProc();}return 0;
}

Linux下的实验代码

#include<sys/types.h>
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<time.h>
#include<sys/wait.h>
#include<sys/shm.h>
#include<sys/sem.h>
#include<string.h>
#define MUTEX 0
#define EMPTY 1
#define FULL 2
#define Length 3
#define WriteProc 2
#define ReadProc 3
#define W_P_Redo 6
#define R_P_Redo 4
#define SEM_KEY 300void P(int sem_id, int sem_num) {struct sembuf xx;xx.sem_num = sem_num;xx.sem_op = -1;xx.sem_flg = 0;semop(sem_id, &xx, 1);
}void V(int sem_id, int sem_num) {struct sembuf xx;xx.sem_num = sem_num;xx.sem_op = 1;xx.sem_flg = 0;semop(sem_id, &xx, 1);
}struct ShrBuf {int array[3];int head;int tail;int IsEmpty;
};//随机停留一定时间
int GetRandomWait() {//srand((unsigned)(getpid() + time(NULL)));return rand() % 5;
}//随机取10以内的整数存入
int GetRandomInput() {srand((unsigned)(getpid() + time(NULL)));return (rand() % 100+1);
}int main() {int sem_id, shm_id, pid, pid2;//信号量id int i, rc;struct ShrBuf *MyBuf;time_t cur;struct tm *p;//init semgetif ((sem_id = semget(SEM_KEY, 3, IPC_CREAT | 0600)) < 0) {printf("main's semget is error\n");exit(-1);}semctl(sem_id, MUTEX, SETVAL, 1);semctl(sem_id, EMPTY, SETVAL, Length);semctl(sem_id, FULL, SETVAL, 0);//创建共享内存if ((shm_id = shmget(IPC_PRIVATE, 24, 0600)) < 0) {printf("error on shget\n");exit(-1);}if ((MyBuf = shmat(shm_id, 0, 0)) == (void*)-1) {printf("error on shmat\n");exit(-1);}MyBuf->head = 0;MyBuf->tail = 0;MyBuf->IsEmpty = 1;memset(MyBuf->array,0,sizeof(MyBuf->array));for (int k = 0; k < WriteProc; k++) {if ((pid = fork()) < 0) {printf("error on fork\n");exit(-1);}if (pid == 0) {//把共享内存加载到进程地址空间if ((MyBuf = shmat(shm_id, 0, 0)) == (void*)-1) {printf("error on shmat\n");exit(-1);}//6次存操作for (i = 0; i < W_P_Redo; i++) {P(sem_id, EMPTY);P(sem_id, MUTEX);sleep(GetRandomWait());MyBuf->array[MyBuf->tail] = GetRandomInput();int num = MyBuf->array[MyBuf->tail];MyBuf->tail = (MyBuf->tail + 1) % Length;MyBuf->IsEmpty = 0;cur = time(NULL);p = localtime(&cur);printf("%d时%d分%d秒\t", p->tm_hour, p->tm_min, p->tm_sec);printf("producerID:%d    puts data:%d \tbuffer:\t%d\t%d\t%d\n", getpid(),num,MyBuf->array[0],MyBuf->array[1],MyBuf->array[2]);V(sem_id, MUTEX);V(sem_id, FULL);}shmdt(MyBuf);exit(0);}}for (int k = 0; k < ReadProc; k++) {if ((pid = fork()) < 0) {printf("error on fork\n");exit(-1);}if (pid == 0) {//把共享内存加载到进程地址空间if ((MyBuf = shmat(shm_id, 0, 0)) == (void*)-1) {printf("error on shmat\n");exit(-1);}//4次读操做for (i = 0; i < R_P_Redo; i++) {P(sem_id, FULL);P(sem_id, MUTEX);sleep(GetRandomWait());				int num = MyBuf->array[MyBuf->head];MyBuf->array[MyBuf->head] = 0;MyBuf->head = (MyBuf->head + 1) % Length;MyBuf->IsEmpty = (MyBuf->head == MyBuf->tail);cur = time(NULL);p = localtime(&cur);printf("%d时%d分%d秒\t", p->tm_hour, p->tm_min, p->tm_sec);printf("customerID:%d    gets data:%d \tbuffer:\t%d\t%d\t%d\n", getpid(), num, MyBuf->array[0], MyBuf->array[1], MyBuf->array[2]);V(sem_id, MUTEX);V(sem_id, EMPTY);}shmdt(MyBuf);exit(0);}}while (wait(0) != -1);shmdt(MyBuf);semctl(sem_id, IPC_RMID, 0);//semctl(sem_id, IPC_RMID, 0);//semctl(sem_id, IPC_RMID, 0);shmctl(shm_id, IPC_RMID, 0);exit(0);
}

http://chatgpt.dhexx.cn/article/bmnqg5VJ.shtml

相关文章

Linux多进程实现生产者消费者问题

1. 任务简介 生产者消费者问题&#xff08;Producer-consumer problem&#xff09;&#xff0c;也称有限缓冲问题&#xff08;Bounded-buffer problem&#xff09;&#xff0c;是一个著名的进程同步问题的经典案例。它描述的是有一组生产者进程在生产产品&#xff0c;并将这些…

信号量与生产者消费者问题

生产者—消费者问题 生产者—消费者题型在各类考试&#xff08;考研、程序员证书、程序员面试笔试、期末考试&#xff09;很常见&#xff0c;原因之一是生产者—消费者题型在实际的并发程序&#xff08;多进程、多线程&#xff09;设计中很常见&#xff1b;之二是这种题型综合性…

Java多种方式解决生产者消费者问题(十分详细)

一、问题描述 生产者消费者问题&#xff08;Producer-consumer problem&#xff09;&#xff0c;也称有限缓冲问题&#xff08;Bounded-buffer problem&#xff09;&#xff0c;是一个多线程同步问题的经典案例。生产者生成一定量的数据放到缓冲区中&#xff0c;然后重复此过程…

生产者消费者问题(多生产多消费,java实现)

生产者消费者问题有多种,本文阐述的是多个生产者生产商品,多个消费者消费商品,缓冲区中有多个商品,这种情况下应该怎么处理线程安全问题 首先,具体用一张图描述一下这种情形,达到的效果是,多个生产者一边生产,多个生产者一边消费。 需要注意两个临界情况 1.缓冲区满的…

Java可视化实现生产者消费者问题

引言:生产者消费者问题是一个十分经典的多线程问题。为了更加形象地描述这个问题&#xff0c;采用可视化的形式展示此过程。 1、问题重述 生产者消费者问题也称有限缓冲问题。该问题描述了两个共享固定大小缓冲区的线程——即所谓的“生产者”和“消费者”在实际运行时会发生的…

C语言 生产者消费者问题

目录 生产者消费者问题算法设计实现源程序测试日志总结 生产者消费者问题 算法设计 实现 1.编写所需头文件 #include<stdio.h> #include<Windows.h>2.定义全局变量 #define productor 2 //生产者数目 为2 #define consumers 3 //消费者数目 为3 #define buffe…

操作系统_生产者消费者问题

目录 1&#xff0c;生产者消费者问题 问题的提出 初步思考 进程资源共享关系和同步关系分析 问题的具体解决 第一搏 存在的问题 第二搏 多维度思考 1&#xff0c;单生产者、单消费者、多缓冲区 2&#xff0c;多生产者、多消费者、单缓冲 3&#xff0c;单生产者、单…

超详解“生产者消费者问题”【操作系统】

目录 一.生产者消费者问题&#xff08;问题描述&#xff09; 二.问题分析 三.背景知识 四.代码实现 五.实验结论 一.生产者消费者问题&#xff08;问题描述&#xff09; 有一个生产者在生产产品&#xff0c;这些产品将提供给一个消费者去消费&#xff0c;为了使生产者和消…

生产者消费者问题

文章目录 1.生产者消费者问题1.1 问题描述1.2 问题分析1.3 如何实现1.4 思考① -> ② -> ③③ -> ④ -> ① 1.5 小结 2.多生产者 - 多消费者2.1 问题描述2.2 问题分析2.3 如何实现2.4 小结 1.生产者消费者问题 1.1 问题描述 系统中有一组生产者进程和一组消费者进…

《操作系统》-生产者消费者问题

什么是生产者消费者问题&#xff1f; 系统中有一组生产者进程和一组消费者进程。生产者进程每次生产一个产品放入缓冲区&#xff0c;消费者进程每次从缓冲区中取出一个进程并使用&#xff0c;那么他们之间具有这样一层关系 生产者、消费者共享一个初始为空、大小为n的缓冲区。…

生产者-消费者问题(操作系统)

生产者-消费者问题从特殊到一般(从易到难)可以分3种形式&#xff1a; 一个生产者、一个消费者、一个缓冲区的问题&#xff1b; 一个生产者、一个消费者、n个缓冲区的问题&#xff1b; k个生产者、m个消费者、n个缓冲区的问题&#xff1b; ★当缓冲区空时&#xff0c;生产者可…

Java多线程——生产者消费者问题

创建多个线程去执行不同的任务&#xff0c;如果这些任务之间有着某种关系&#xff0c;那么线程之间必须能够通信来协调完成工作。 生产者消费者问题&#xff08;英语&#xff1a;Producer-consumer problem&#xff09;就是典型的多线程同步案例&#xff0c;它也被称为有限缓冲…

生产者-消费者问题(详解)

目录 1.问题描述 2.问题分析 3.问题实现 3.1 初始化 3.2 生产者 3.3 消费者 1.问题描述 要求如下&#xff1a; 只要缓冲区没满&#xff0c;生产者才能把产品放入缓冲区&#xff0c;否则必须等待。只有缓冲区不空时&#xff0c;消费者才能从中取出产品&#xff0c;否则必…

【操作系统】生产者消费者问题

生产者消费者模型 文章目录 生产者消费者模型 [toc]一、 生产者消费者问题二、 问题分析三、 伪代码实现四、代码实现&#xff08;C&#xff09;五、 互斥锁与条件变量的使用比较 一、 生产者消费者问题 生产者消费者问题&#xff08;英语&#xff1a;Producer-consumer proble…

Sublime Text实现代码自动生成,快速编写HTML/CSS代码

目录 下载Sublime Text安装emmet插件常用自动生成HTML代码实例初始化页面自动补全标签配对自动添加类名和id名自动填充文本内容自动生成同级标签自动生成嵌套标签自动生成提级标签自动生成分组标签自动生成多个元素自动生成带多个属性的元素自动生成隐式标签 常用自动生成CSS代…

MybatisPlus代码自动生成

这里写自定义目录标题 前言一. 什么是 MyBatis-Plus二.MybatisPlus 代码自动生成①idea 插件生成1. 插件2.连接数据源3.生成代码 ②配置工具类生成 前言 最开始&#xff0c;要在 Java 中使用数据库时&#xff0c;需要使用 JDBC&#xff0c;创建 Connection、ResultSet 等&…

Simulink自动代码生成:生成代码的基本设置

Simulink自动代码生成也被称作基于模型开发&#xff08;BMD&#xff09;&#xff0c;相比于传统的手写代码方式能够尽量减少人为错误。模型本身可以用于仿真&#xff0c;单元测试等&#xff0c;更便于提前发现逻辑错误。同时只要约定好模型接口&#xff0c;就可以多人协作&…

C语言代码自动生成工具

一、模型建模模块&#xff1a; 基于开源开发平台Eclipse&#xff0c;以图形方式创建和编辑模型元素&#xff0c;模型元素如下&#xff1a; 活动&#xff1a;初始活动、简单活动、复杂活动、结束活动&#xff1b;状态&#xff1a;初始状态、状态、结束状态&#xff1b;变迁&a…

前端代码自动生成器

场景 1.CodeFun是什么 CodeFun是一款UI 设计稿智能生成源代码的工具,支持微信小程序端、移动端H5和混合APP,上传 Sketch、PSD等形式的设计稿&#xff0c;通过智能化技术一键生成可维护的前端代码. 2.学习成本高吗&#xff1f; 对于前端工程师来说&#xff0c;几乎没有学习成本…

MATLAB/Simulink自动代码生成(一)

Simulink自带了种类繁多、功能强大的模块库&#xff0c;在基于模型设计的开发流程下&#xff0c;Simulink不仅通过仿真可以进行早期设计的验证&#xff0c;还可以生成C/C、PLC等代码直接应用于PC、MCU、DSP等平台。在嵌入式软件开发中发挥着重要的作用&#xff0c;本文以Simuli…