Copyright © 2024 Squareroot_2, All rights reserved.
实验二 生产者消费者问题
一、实验目的
通过本实验,加深对进程概念的理解,明确进程与程序的区别;认识并发执行的本质,理解和掌握Windows进程通信系统调用的功能,通过实验掌握使用信号量机制完成进程间同步和互斥的方法。
二、实验内容
• 创建一个有6个缓冲区的缓冲池,初始为空,每个缓冲区能存放一个长度若为10个字符的字符串。
• 2个生产者进程
– 随机等待一段时间,往缓冲区添加数据,
– 若缓冲区已满,等待消费者取走数据后再添加
– 重复12次
• 3个消费者进程
– 随机等待一段时间,从缓冲区读取数据
– 若缓冲区为空,等待生产者添加数据后再读取
– 重复8次
• 在Windows平台上做。
• 显示每次添加或读取数据的时间及缓冲区的映像。
• 生产者和消费者用进程模拟。
三、程序设计与实现
#include <iostream>
#include <windows.h>
#define BUFSIZE 6 //缓冲区大小
#define STRLEN 10 //字符串长度
#define PRO_TYPE 0 //生产者标识
#define PROERNUM 2 //生产者数量
#define PRONUM 12 //每个生产者生产的数据量
#define CON_TYPE 1 //消费者标识
#define CONERNUM 3 //消费者数量
#define CONNUM 8 //每个消费者消费的数据量
static LPCTSTR filemapping_name = "FileMapping";
HANDLE ProcessHandle[5];
using namespace std;
//缓存结构体
typedef struct BUF {
char buf[BUFSIZE][STRLEN + 1];
int head;
int tail;
} buffer;
//返回随机休眠时间
int getRandomSleep() {
return rand() % 1000 + 100;
}
//返回随机字符
char getRandomChar() {
return (rand() % 26) + 'A';
}
//定义P操作
DWORD P(HANDLE S) {
return WaitForSingleObject(S, INFINITE);
}
//定义V操作
BOOL V(HANDLE S) {
return ReleaseSemaphore(S, 1, nullptr);
}
//创建子进程
void startChildProcess(const int type, const int id) {
TCHAR filename[MAX_PATH];
GetModuleFileName(nullptr, filename, MAX_PATH);
TCHAR cmdLine[MAX_PATH];
sprintf(cmdLine, "\"%s\" %d", filename, type);
STARTUPINFO si = {sizeof(STARTUPINFO)};
PROCESS_INFORMATION pi;
//新建子进程
BOOL bCreateOK = CreateProcess(
filename,
cmdLine,
nullptr,
nullptr,
FALSE,
CREATE_DEFAULT_ERROR_MODE,
nullptr,
nullptr,
&si,
&pi);
if (bCreateOK) {
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
ProcessHandle[id] = pi.hProcess;
} else {
printf("child process error!\n");
exit(0);
}
}
//主进程程序
void useParentProcess() {
//创建信号量
HANDLE MUTEX = CreateSemaphore(nullptr,
1,
1,
"mutex");
HANDLE EMPTY = CreateSemaphore(nullptr,
BUFSIZE,
BUFSIZE,
"empty");
HANDLE FULL = CreateSemaphore(nullptr,
0,
BUFSIZE,
"full");
//创建共享内存
HANDLE hMapping = CreateFileMapping(
nullptr,
nullptr,
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 != nullptr) {
ZeroMemory(pFile, sizeof(struct BUF));
}
//共享内存的初始化
buffer *pBuf = reinterpret_cast<buffer *>(pFile);
memset(pBuf->buf, 0, sizeof(pBuf->buf));
pBuf->head = 0;
pBuf->tail = 0;
//解除映射
UnmapViewOfFile(pFile);
pFile = nullptr;
CloseHandle(pFile);
}
//创建2个生产者,3个消费者
int childProcessNum = PROERNUM + CONERNUM;
for (int i = 0; i < childProcessNum; ++i) {
int type = i < PROERNUM ? PRO_TYPE : CON_TYPE;
startChildProcess(type, i);
}
//等待子进程运行
WaitForMultipleObjects(childProcessNum,
ProcessHandle,
TRUE,
INFINITE);
//回收信号量
CloseHandle(EMPTY);
CloseHandle(FULL);
CloseHandle(MUTEX);
}
//生产者
void useProducerProcess(HANDLE MUTEX, HANDLE EMPTY, HANDLE FULL, buffer *pbuf) {
for (int i = 0; i < PRONUM; ++i) {
P(EMPTY);
Sleep(getRandomSleep());
P(MUTEX);
//生产数据
char string[STRLEN + 1];
memset(string, 0, sizeof(string));
for (int j = 0; j < STRLEN; ++j) {
string[j] = getRandomChar();
}
strcpy(pbuf->buf[pbuf->tail], string);
pbuf->tail = (pbuf->tail + 1) % BUFSIZE;
//打印时间
time_t t = time(nullptr);
struct tm *ptm = localtime(&t);
//打印生产数据、时间和缓冲区映像
printf("\nProducerID:%6d, time: %d:%d:%d, puts data:%-10s\tbuffer 1:%-10s\t2:%-10s\t3:%-10s\t4:%-10s\t5:%-10s\t6:%-10s",
(int) GetCurrentProcessId(),
ptm->tm_hour, ptm->tm_min, ptm->tm_sec,
string, pbuf->buf[0], pbuf->buf[1], pbuf->buf[2],
pbuf->buf[3], pbuf->buf[4], pbuf->buf[5]);
//刷新缓冲区
fflush(stdout);
V(MUTEX);
V(FULL);
}
}
//消费者
void useCustomerProcess(HANDLE MUTEX, HANDLE EMPTY, HANDLE FULL, buffer *pbuf) {
for (int i = 0; i < CONNUM; ++i) {
P(FULL);
Sleep(getRandomSleep());
P(MUTEX);
//消费数据
char string[STRLEN + 1];
memset(string, 0, sizeof(string));
strcpy(string, pbuf->buf[pbuf->head]);
memset(pbuf->buf[pbuf->head], 0, sizeof(pbuf->buf[pbuf->head]));
pbuf->head = (pbuf->head + 1) % BUFSIZE;
//打印时间
time_t t = time(nullptr);
struct tm *ptm = localtime(&t);
//打印消费数据、时间和缓冲区映像
printf("\nCustomerID:%6d, time: %d:%d:%d, gets data:%-10s\tbuffer 1:%-10s\t2:%-10s\t3:%-10s\t4:%-10s\t5:%-10s\t6:%-10s",
(int) GetCurrentProcessId(),
ptm->tm_hour, ptm->tm_min, ptm->tm_sec,
string, pbuf->buf[0], pbuf->buf[1], pbuf->buf[2],
pbuf->buf[3], pbuf->buf[4], pbuf->buf[5]);
//刷新缓冲区
fflush(stdout);
V(EMPTY);
V(MUTEX);
}
}
//子进程程序
void useChildProcess(int type) {
//打开信号量
HANDLE MUTEX = OpenSemaphore(SEMAPHORE_ALL_ACCESS,
FALSE,
"mutex");
HANDLE EMPTY = OpenSemaphore(SEMAPHORE_ALL_ACCESS,
FALSE,
"empty");
HANDLE FULL = OpenSemaphore(SEMAPHORE_ALL_ACCESS,
FALSE,
"full");
//打开共享内存区,并加载到当前进程地址空间
HANDLE hmap = OpenFileMapping(
FILE_MAP_ALL_ACCESS,
FALSE,
filemapping_name);
LPVOID pFile = MapViewOfFile(
hmap,
FILE_MAP_ALL_ACCESS,
0,
0,
0);
buffer *pBuf = reinterpret_cast<buffer *>(pFile);
//根据参数判断子进程是生产者或消费者
if (type == PRO_TYPE) {
useProducerProcess(MUTEX, EMPTY, FULL, pBuf);
} else if (type == CON_TYPE) {
useCustomerProcess(MUTEX, EMPTY, FULL, pBuf);
} else {
printf("maybe child process error!\n");
exit(-1);
}
//解除映射
UnmapViewOfFile(pFile);
pFile = nullptr;
CloseHandle(pFile);
//关闭信号量
CloseHandle(MUTEX);
CloseHandle(EMPTY);
CloseHandle(FULL);
}
int main(int argc, char *argv[]) {
if (argc > 1) {
//有参数则为子进程
srand(GetCurrentProcessId());
int type = atoi(argv[1]);
useChildProcess(type);
} else {
//无参数则为主进程
useParentProcess();
system("pause");
}
return 0;
}
四、实验结果及分析
将以上代码编译运行后得到以下结果:
本次实验中,通过进程模拟生产者和消费者:
生产者进程ID:6172、1200
消费者进程ID:520、11216、11228
共计5个子线程,完成24次字符串生产和24次字符串消费.
五、实验收获与体会
“生产者消费者问题”是信号量知识里非常经典的案例,通过本次实验,我进一步加深了对进程概念的理解,深刻认识到并发执行的本质,理解和掌握Windows进程通信系统调用的功能,掌握了使用信号量机制完成进程间同步和互斥的方法。