实验内容:模拟实现用同步机构避免并发进程执行时可能出现的与时间有关的错误。
实验目的:进程是程序在一个数据集合上运行的过程,进程是并发执行的,也即系统中的多个进 程轮流地占用处理器运行。 我们把如干个进程都能进行访问和修改地那些变量成为公共变量。由于进程是并发执 行的,所以,如果对进程访问公共变量不加限制,那么就会产生“与时间有关”的错误,即 进程执行后,所得到的结果与访问公共变量的时间有关。为了防止这类错误,系统必须要用 同步机构来控制进程对公共变量的访问。一般说,同步机构是由若干条原语——同步原语— —所组成。本实验要求学生模拟 PV 操作同步机构的实现,模拟进程的并发执行,了解进程 并发执行时同步机构的作用。
注: 编程语言 C/C++
编译环境:Vs code
网上代码较多,但我所查询到的(包括我借鉴的)均是有错误的。像循环数组输出存在问题,阻塞队列指针与就绪队列指针混乱错误,我对其进行了修正。
运行结果截图:
源代码:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#define BUFSIZE 3 // 缓存的大小
#define MAXSTRSIZE 10 // 最大可以输入的字符串长度
struct PCB
{
char name[10]; // 进程名
char status[10]; // 运行状态
char blockReason[5]; // 若阻塞,其原因
int breakPoint; // 断点保护
PCB *next; // 指向下一个进程
} * produceProcess, *consumeProcess; // 生产者和消费者进程
struct semaphore // 信号量
{
int value; // 信号量的值
PCB *queue; // 阻塞队列
} s1, s2;
PCB *queue_ready = new PCB; // 就绪队列,队首指向运行的进程
char str[MAXSTRSIZE]; // 输入的字符串
char buffer[BUFSIZE + 1]; // 缓冲池
int len; // 输入长度
int stringPointer = 0; // string的指针
int producePointer = 0; // 生产者指针
int consumePointer = 0; // 消费者指针
char temp; // 供打印的临时产品
char recordProduce[MAXSTRSIZE]; // 生产记录
int recordProducePointer = 0; // 生产记录指针
char recordConsume[MAXSTRSIZE]; // 消费记录
int recordConsumePointer = 0; // 消费记录指针
int pc = 0; // 程序计数器
int count_characters; // 字符计数器
int count_consume; // 消费计数器
void block(int s) // 阻塞函数的定义
{
PCB *p;
int queueNum_produce = 0;
int queueNum_consume = 0;
if (s == 1) // 生产进程
{
strcpy(produceProcess->status, "Block"); // 变为阻塞态
strcpy(produceProcess->blockReason, "full"); // 说明原因
p = s1.queue;
while (p->next != NULL)
{
queueNum_produce++;
p = p->next; // 找到最后一个
}
if (s1.queue->next == NULL)
s1.queue->next = produceProcess;
else
p->next = produceProcess; // 放入阻塞队列
queue_ready->next->breakPoint = pc; // 保存断点
queue_ready->next = queue_ready->next->next; // 在就绪队列中去掉,指向下一个
produceProcess->next = NULL;
printf("\t* produceProcess生产进程阻塞了!\n");
queueNum_produce++;
}
else // 消费进程
{
strcpy(consumeProcess->status, "Block"); // 变为阻塞态
strcpy(consumeProcess->blockReason, "empty"); // 说明原因
p = s2.queue;
while (p->next)
{
queueNum_consume++;
p = p->next; // 找到最后一个
}
if (s2.queue->next == NULL)
s2.queue->next = consumeProcess;
else
p->next = consumeProcess; // 放入阻塞队列
queue_ready->next->breakPoint = pc; // 保存断点
queue_ready->next = queue_ready->next->next; // 在就绪队列中去掉,指向下一个
consumeProcess->next = NULL;
printf("\t* consumeProcess消费进程阻塞了!\n");
queueNum_consume++;
}
printf("\t* 阻塞的生产进程个数为:%d\n", queueNum_produce);
printf("\t* 阻塞的消费进程个数为:%d\n", queueNum_consume);
}
void wakeup(int s) // 唤醒函数的定义
{
PCB *p;
PCB *q = queue_ready;
if (s == 1) // 唤醒s1.queue队首进程,生产进程队列
{
p = s1.queue->next;
s1.queue->next = p->next; // 阻塞指针指向下一个阻塞进程
strcpy(p->status, "Ready");
strcpy(p->blockReason, "Null");
while (q->next) // 插入就绪队列
q = q->next;
q->next = p;
p->next = NULL;
printf("\t* p1生产进程唤醒了!\n");
}
else // 唤醒s2.queue队首进程,消费进程队列
{
p = s2.queue->next;
s2.queue->next = p->next; // 阻塞指针指向下一个阻塞进程
strcpy(p->status, "Ready");
strcpy(p->blockReason, "Null");
while (q->next) // 插入就绪队列
q = q->next;
q->next = p;
p->next = NULL;
printf("\t* c1消费进程唤醒了!\n");
}
}
void init() // 初始化
{
s1.value = BUFSIZE;
s2.value = 0;
produceProcess = (PCB *)malloc(sizeof(PCB)); // 建立新的结点,并初始化为生产者
strcpy(produceProcess->name, "Producer"); // 初始化名字
strcpy(produceProcess->status, "Ready"); // 初始化状态
strcpy(produceProcess->blockReason, "Null");
produceProcess->breakPoint = 0; // 断点保护为0
produceProcess->next = NULL;
consumeProcess = (PCB *)malloc(sizeof(PCB)); // 建立新的结点,并初始化为消费者
strcpy(consumeProcess->name, "Consumer"); // 初始化名字
strcpy(consumeProcess->status, "Ready"); // 初始化状态
strcpy(consumeProcess->blockReason, "Null");
consumeProcess->breakPoint = 0; // 断点保护为0
consumeProcess->next = NULL;
queue_ready->next = produceProcess;
queue_ready->next->next = consumeProcess; // 初始化为生产进程在前,消费进程在后
queue_ready->next->next->next = NULL;
s1.queue = (PCB *)malloc(sizeof(PCB));
s2.queue = (PCB *)malloc(sizeof(PCB)); // 信号量阻塞进程为NULL
s1.queue->next = NULL;
s2.queue->next = NULL;
pc = 0; // 初始化pc为0
count_consume = 0; // 初始消费计数器为0
}
void P(int s)
{
if (s == 1) // P(s1)
{
s1.value--;
if (s1.value < 0)
block(1); // 阻塞当前生产进程
else
{
printf("\t* s1信号申请成功!\n");
queue_ready->next->breakPoint = pc; // 保存断点
}
}
else // P(s2)
{
s2.value--;
if (s2.value < 0)
block(2); // 阻塞当前消费进程
else
{
printf("\t* s2信号申请成功!\n");
queue_ready->next->breakPoint = pc; // 保存断点
}
}
}
void V(int s)
{
if (s == 1) // V(s1)
{
s1.value++;
if (s1.value <= 0)
wakeup(1); // 唤醒生产进程
queue_ready->next->breakPoint = pc; // 保存断点
}
else // V(s2)
{
s2.value++;
if (s2.value <= 0)
wakeup(2); // 唤醒消费进程
queue_ready->next->breakPoint = pc; // 保存断点
}
}
void processorScheduling() // 处理器调度程序
{
int rd;
int readyProcessNumber = 0; // 就绪进程个数,取值为1或2
PCB *p = queue_ready;
if (queue_ready->next == NULL) // 若无就绪进程,return
return;
while (p->next) // 统计就绪进程个数
{
readyProcessNumber++;
p = p->next;
}
printf("\t* 就绪进程个数为:%d\n", readyProcessNumber);
srand((unsigned)time(NULL)); // 随机数种子
rd = rand() % readyProcessNumber; // 随机函数产生随机数,取值范围为0~1
if (rd == 1)
{
p = queue_ready->next;
queue_ready->next = queue_ready->next->next;
queue_ready->next->next = p;
p->next = NULL; // 将queue_ready->next与queue_ready->next->next交换
strcpy(queue_ready->next->status, "Runing"); // 将queue_ready->next状态改为Runing
strcpy(queue_ready->next->next->status, "Ready"); // 将queue_ready->next->next状态改为Ready
}
else
strcpy(queue_ready->next->status, "Runing"); // 将queue_ready->next状态改为Runing
pc = queue_ready->next->breakPoint;
}
void processorExecution() // 模拟处理器指令执行
{
if (strcmp(queue_ready->next->name, "Producer") == 0) // 当前进程为生产者
switch (pc)
{
case 0: // produce
printf("\t* 生产者生产了字符%c\n", str[stringPointer]);
recordProduce[recordProducePointer] = str[stringPointer]; // 添加到生产记录
stringPointer = (stringPointer + 1) % len;
pc++;
queue_ready->next->breakPoint = pc; // 保存断点
break;
case 1: // p(s1)
pc++;
P(1);
break;
case 2: // put
buffer[producePointer] = recordProduce[recordProducePointer]; // 放到缓冲区
printf("\t* %c字符成功入驻空缓存!\n", buffer[producePointer]);
recordProducePointer++;
producePointer = (producePointer + 1) % (BUFSIZE + 1);
pc++;
queue_ready->next->breakPoint = pc; // 保存断点
break;
case 3: // v(s2)
pc++;
printf("\t* 释放一个s2信号\n");
V(2);
break;
case 4: // goto 0
printf("\t* 生产进程goto 0 操作\n");
pc = 0;
count_characters--; // 剩余字符个数减1
printf("\t* 剩余字符count=%d个\n", count_characters);
queue_ready->next->breakPoint = pc; // 保存断点
if (count_characters <= 0) // 生产结束
{
printf("\t* 生产者结束生产!\n");
strcpy(produceProcess->status, "Stop"); // 生产者状态改为Stop
strcpy(produceProcess->blockReason, "Null"); // 生产者阻塞原因改为Null
produceProcess->breakPoint = -1; // 生产者断点改为-1
queue_ready->next = queue_ready->next->next; // 在就绪队列中去掉
}
}
else // 当前进程为消费者
switch (pc)
{
case 0: // p(s2)
pc++;
P(2);
break;
case 1: // get
printf("\t* 消费者取字符!\n");
temp = buffer[consumePointer];
consumePointer = (consumePointer + 1) % (BUFSIZE + 1);
pc++;
queue_ready->next->breakPoint = pc; // 保存断点
break;
case 2: // v(s1)
pc++;
printf("\t* 释放一个s1\n");
V(1);
break;
case 3: // consume
printf("\t* 消费了字符%c\n", temp);
recordConsume[recordConsumePointer] = temp; // 添加到消费记录
recordConsumePointer++;
count_consume++;
if (count_consume >= len)
{
strcpy(consumeProcess->status, "Stop"); // 完成态
consumeProcess->breakPoint = -1;
return;
}
pc++;
queue_ready->next->breakPoint = pc; // 保存断点
break;
case 4: // goto0
printf("\t* 消费进程goto 0 操作\n");
pc = 0;
queue_ready->next->breakPoint = pc; // 保存断点
}
}
void showProcessCondition()
{
printf("-----------------Producer consumer simulation-----------------\n");
printf("* The string for the simulation procedure is:\t");
printf("%s\n", str);
printf("* 已生产:");
for (int i = 0; i < strlen(recordProduce); i++)
printf("%c", recordProduce[i]);
printf("\n* 空缓存:");
if (consumePointer < producePointer)
for (int i = consumePointer; i < producePointer; i++)
printf("%c", buffer[i]);
else if (consumePointer > producePointer)
{
for (int i = consumePointer; i < (BUFSIZE + 1); i++)
printf("%c", buffer[i]);
for (int i = 0; i < producePointer; i++)
printf("%c", buffer[i]);
}
printf("\n* 已消费:");
for (int i = 0; i < strlen(recordConsume); i++)
printf("%c", recordConsume[i]);
printf("\n----------------------Information of PCB----------------------\n");
printf("进程名\t\t状态\t\t等待原因\t断点\n");
printf("%s\t%s\t\t%s\t\t%d\n\n", produceProcess->name, produceProcess->status, produceProcess->blockReason, produceProcess->breakPoint);
printf("%s\t%s\t\t%s\t\t%d\n", consumeProcess->name, consumeProcess->status, consumeProcess->blockReason, consumeProcess->breakPoint);
printf("------------------------------------------This is the dividing line.--------------------------------------------------\n");
}
int main()
{
bool runForAll = false;
char choice;
printf("-----------Producer consumer simulation-----------\n");
printf("* Please enter a string:\n");
do
{
scanf("%s", &str);
getchar();
len = strlen(str);
if (len > MAXSTRSIZE)
{
printf("* The string is too long!\n");
printf("* Please enter a string:\n");
}
} while (len > MAXSTRSIZE);
count_characters = len; // 输入字符的个数
init(); // 初始化
while (count_consume < len) // 消费完所有的字符为结束
{
printf("*************The flow of instructions*************\n");
processorScheduling(); // 处理器调度程序
processorExecution(); // 模拟处理器指令执行
showProcessCondition(); // 输出显示各个信息
if (!runForAll)
{
printf("0. Exit 1. Directly running others. Step by step\n");
scanf("%c", &choice);
if (choice == '0')
exit(0);
else if (choice == '1')
runForAll = true;
}
}
printf("\nAll elements in a string sequence have been produced and consumed.\n");
return 0;
}