要求
- 在 windows 环境下,利用高级语言编程环境(限定为 VS 环境或 VC 环境)调用 CreateThread 函数和相关的同步函数,模拟实现“生产者-消费者”问题。“生产者-消费者”模拟实验的具体要求见后面附件。
知识点
CRITICAL_SECTION cs
: 创建一个临界区变量,其中EnterCriticalSection(&cs)
与LeaveCriticalSection(&cs)
之间的区域就是临界区,临界资源是一次仅允许一个进程或线程使用的共享资源,这样就保证了不能同时进行生产和消费操作。- CreateEvent()函数:
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes
,// 安全属性
BOOLbManualReset
,// 复位方式,如果是TRUE,那么必须用ResetEvent函数来手工将事件的状态复原到无信号状态,使用ResetEvent()
重置信号状态。如果设置为FALSE,当一个等待线程被释放以后,系统将会自动将事件状态复原为无信号状态。
BOOLbInitialState
,// 初始状态,如果为TRUE,初始状态为有信号状态;否则为无信号状态。
LPCTSTRlpName
// 对象名称
);
解析
- 定义全局变量
- 生产者进程
- 消费者进程
- 创建两个生产者线程、三个消费者线程
结果分析
- 输出
如图所示,生产者生产的对应数据永远在消费数据之前,且因为我把缓冲区当做一个队列处理,所以生产数据的顺序与消费数据的顺序对应。还能看出生产者1生产的数据在1000-1999内,生产者2生产数据在2000-2999内,符合题目要求。
- 文件输出
如图所示,可知文件中记录的信息与控制台中输出的信息一样,文件写入无误。
完整源码
#include<stdio.h>
#include<Windows.h>
#include <stdlib.h>
#include <time.h>
#include <io.h>
const int PRODUCT_NUM = 100;//产品数量
CRITICAL_SECTION cs;//定义临界区全局变量
HANDLE emptyEvent;//存在空位事件
HANDLE fullEvent;//存在数据事件
static int full = 0;//缓冲区中数据个数
static int empty = 10;//缓冲区中空位数量
static int Buffer[10];//存储数据的缓冲区
static int len = 10;//缓冲区长度
//将缓冲区看成一个队列
static int tail = -1;//缓冲区尾指针
static int head = 0;//缓冲区头指针
//产品与消费计数器
static int producerCount = 0;
static int consumerCount = 0;
//写入文件
static FILE* file;
int P(int *i) {
return --*i;
}
int V(int *i) {
return ++*i;
}
DWORD WINAPI ProducerThread(void* a) {
int data = 0;
int* dataRange = (int *)a;//数据范围
while(TRUE) {
if (producerCount >= PRODUCT_NUM) {
break;
}
//以下两步相当于P操作
P(&empty);
//没有空位,等待消费者消费数据才能往下进行
if (empty <= 0) {
WaitForSingleObject(emptyEvent, INFINITE);
}
//进入临界区
EnterCriticalSection(&cs);
data = rand() % (dataRange[1] - dataRange[0] + 1) + dataRange[0];//随机数据
Buffer[(++tail) % len] = data;
printf("生产者%d生产第%d个数据%d.\n",dataRange[2], ++producerCount, data);
//写入文件
fprintf(file, "生产者%d生产第%d个数据%d.\n", dataRange[2], producerCount, data);
LeaveCriticalSection(&cs);
Sleep(rand() % 100 + 1);//随机睡眠1-100ms
V(&full);
if (full > 0) {
SetEvent(fullEvent);
}
}
return 0;
}
DWORD WINAPI ConsumerThread(void* a) {
int data = 0;
int *flag = (int *)a;//消费者编号
while(TRUE) {
if (consumerCount >= PRODUCT_NUM) {
break;
}
//以下两步相当于P操作
P(&full);
//没有数据,等待生产者生产数据才能往下进行
if (full <= 0) {
WaitForSingleObject(fullEvent, INFINITE);
}
//进入临界区
EnterCriticalSection(&cs);
data = Buffer[head % len];
head = (head + 1) % len;
printf("\t\t\t消费者%d消费第%d个数据%d.\n",*flag, ++consumerCount, data);
//写入文件
fprintf(file, "\t\t\t消费者%d消费第%d个数据%d.\n", *flag, consumerCount, data);
LeaveCriticalSection(&cs);
Sleep(rand() % 100 + 1);//随机睡眠1-100ms
V(&empty);
if (empty > 0) {
SetEvent(emptyEvent);
}
}
return 0;
}
int main() {
srand((unsigned int)(time(NULL)));
InitializeCriticalSection(&cs);//初始化临界区变量
emptyEvent = CreateEvent(NULL, false, true, NULL);
fullEvent = CreateEvent(NULL, false, false, NULL);
static char* fileName = "E://操作系统原理练习/第二次上机/log.txt";
fopen_s(&file, fileName, "r+");
HANDLE handle[5];
int a1[3] = { 1000,1999,1};//生产者1产生数据范围1000-1999,最后一个数是对应编号
int a2[3] = { 2000, 2999,2};//生产者2产生数据范围2000-2999,最后一个数是对应标号
//创建生产线程
handle[0] = CreateThread(NULL, 0, ProducerThread, a1, 0, NULL);
handle[1] = CreateThread(NULL, 0, ProducerThread, a2, 0, NULL);
//对消费者进行标号
int *flag1 = (int *)malloc(sizeof(int *)), *flag2 = (int *)malloc(sizeof(int *)), *flag3 = (int *)malloc(sizeof(int *));
*flag1 = 1;
*flag2 = 2;
*flag3 = 3;
//消费者线程
handle[2] = CreateThread(NULL, 0, ConsumerThread, flag1, 0, NULL);
handle[3] = CreateThread(NULL, 0, ConsumerThread, flag2, 0, NULL);
handle[4] = CreateThread(NULL, 0, ConsumerThread, flag3, 0, NULL);
WaitForMultipleObjects(5, handle, TRUE, INFINITE);
fclose(file);
getchar();
return 0;
}