操作系统实验三——生产者-消费者问题

生产者消费者问题

基本原理

在 Linux 操作系统用 C 实现经典同步问题:生产者—消费者,具体要求如下:
① 一个大小为 10 的缓冲区,初始状态为空。
② 2个生产者,随机等待一段时间,往缓冲区中添加数据,若缓冲区已满,等待消费者取走数据之后再添加,重复 10 次。
③ 2个消费者,随机等待一段时间,从缓冲区中读取数据,若缓冲区为空,等待生产者添加数据之后再读取,重复 10 次。

实验要求

本实验的主要目的是模拟操作系统中进程同步和互斥。在系统进程并发执行异步推进的过程中,由于资源共享和进程间合作而造成进程间相互制约。进程间的相互制约有两种不同的方式。
① 间接制约。这是由于多个进程共享同一资源(如 CPU、共享输入/输出设备)而引起的,即共享资源的多个进程因系统协调使用资源而相互制约。
② 直接制约。只是由于进程合作中各个进程为完成同一任务而造成的,即并发进程各自的执行结果互为对方的执行条件,从而限制各个进程的执行速度。

生产者和消费者是经典的进程同步问题,在这个问题中,生产者不断的向缓冲区中写入数据,而消费者则从缓冲区中读取数据。生产者进程和消费者对缓冲区的操作是互斥,即当前只能有一个进程对这个缓冲区进行操作,生产者进入操作缓冲区之前,先要看缓冲区是否已满,如果缓冲区已满,则它必须等待消费者进程将数据取出才能写入数据,同样的,消费者进程从缓冲区读取数据前,也要判断缓冲区是否为空,如果为空,则必须等待生产者进程写入数据才能读取数据。

在本实验中,进程之间要进行通信来操作同一缓冲区。一般来说,进程间的通信根据通信内容可以划分为两种:即控制信息的传送与大批量数据传送。有时,也把进程间控制信息的交换称为低级通信,而把进程间大批量数据的交换称为高级通信。
目前,计算机系统中用得比较普遍的高级通信机制可分为 3 大类:共享存储器系统、消息传递系统及管道通信系统。

共享存储器系统

共享存储器系统为了传送大量数据,在存储器中划出一块共享存储区,诸进程可通过对共享存储区进行读数据或写数据以实现通信。进程在通信之前,向系统申请共享存储区中的一个分区,并为它指定一个分区关键字。
消息传递系统
在消息传递系统中,进程间的数据交换以消息为单位,在计算机网络中被称为报文。消息传递系统的实现方式又可以分为以下两种:

直接通信方式

发送进程可将消息直接发送给接收进程,即将消息挂在接收进程的消息缓冲队列上,而接收进程可从自己的消息缓冲队列中取得消息。

间接通信方式

发送进程将消息发送到指定的信箱中,而接收进程从信箱中取得消息。这种通信方式又称信箱通信方式,被广泛地应用于计算机网络中。相应地,该消息传递系统被称为电子邮件系统。

管道通信系统

向管道提供输入的发送进程,以字符流方式将大量的数据送入管道,而接收进程从管道中接收数据。由于发送进程和接收进程是利用管道进行通信的,故称为管道通信。
为了协调发送和接收双方的通信,管道通信机制必须提供以下3方面的协调功能。
① 互斥
当一个进程正在对 pipe 文件进行读或写操作时,另一个进程必须等待。
② 同步
当写进程把一定数量的数据写入 pipe 文件后,便阻塞等待,直到读进程取走数据后,再把写进程唤醒。
③ 确认对方是否存在
只有确定对方已存在时,才能进行管道通信,否则会造成因对方不存在而无限制地等待。

在这个问题当中,我们采用信号量机制进行进程之间的通信,设置两个信号量,空的信号量和满的信号量。
在 Linux 系统中,一个或多个信号量构成一个信号量集合。使用信号量机制可以实现进程之间的同步和互斥,允许并发进程一次对一组信号量进行相同或不同的操作。每个 P、V 操作不限于减 1 或加 1,而是可以加减任何整数。在进程终止时,系统可根据需要自动消除所有被进程操作过的信号量的影响
① 缓冲区采用循环队列表示,利用头、尾指针来存放、读取数据,以及判断队列是否为空。缓冲区中数组大小为 10;
② 利用随机函数 rand()得到 A~Z 的一个随机字符,作为生产者每次生产的数据,存放到缓冲区中;
③ 使用 shmget()系统调用实现共享主存段的创建, shmget()返回共享内存区的 ID。对于已经申请到的共享段,进程需把它附加到自己的虚拟空间中才能对其进行读写。
④ 信号量的建立采用 semget()函数,同时建立信号量的数量。在信号量建立后,调用 semctl()对信号量进行初始化,例如本实验中,可以建立两个信号量 SEM_EMPTY、 SEM_FULL,初始化时设置 SEM_EMPTY 为 10, SEM_FULL 为 0。使用操作信号的函数 semop()做排除式操作,使用这个函数防止对共享内存的同时操作。对共享内存操作完毕后采用 shmctl() 函数撤销共享内存段。
⑤ 使用循环,创建 2 个生产者以及 2 个消费者,采用函数 fork()创建一个新的进程。
⑥ 一个进程的一次操作完成后,采用函数 fflush()刷新缓冲区。
⑦ 程序最后使用 semctl()函数释放内存。
(3)模拟程序流程图
① 主程序流程图:
在这里插入图片描述

② 生产者进程流程图:
在这里插入图片描述

③ 消费者进程流程图:
在这里插入图片描述

④ P操作流程图:
在这里插入图片描述

⑤ V操作流程图:
在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <time.h>
//宏定义一些变量和信号量,方便进行调试

#define N 10			 //缓冲区的大小
#define ProNum 2		 //生产者的数量
#define ConNum 2		 //消费者的数量
#define SleepTime 3		 //生产者消费者操作完后的暂停时间
typedef int semaphore;	 //定义信号量的类型均为整数
typedef char element;	 //定义生产者生产的元素都是字符
element buffer[N] = {0}; //定义长度为 N 的缓冲区,内容均是字符型
int in = 0;				 //缓冲区下一个存放产品的索引
int out = 0;			 //缓冲区下一个可以使用的产品索引
int ProCount = 0;		 //生产者序号统计
int ConCount = 0;		 //消费者序号统计
double StartTime;		 //程序开始时间记录,方便输出执行语句的时间

//定义并初始化信号量
//mutex 用来控制生产和消费每个时刻只有一个在进行,初始化为 1,用来实现互斥
//empty 表示剩余的可用来存放产品的缓冲区大小,用来实现同步,刚开始有 N 个空位置
//full 表示当前缓冲区中可用的产品数量,用来实现同步,刚开始没有产品可以使用
semaphore mutex = 1, empty = N, full = 0;

//生产者线程
void *Producer(void *args)
{
	int *x = (int *)args;
	element eletemp;
	int sum = 0;
	while (sum < 10)
	{
		while (empty <= 0) //P(empty),判断是否有空位置供存放生产的产品,同步,没有空位则等待并输出提示语句
			printf("%.4lfs| 缓 冲 区 已 满 ! 生 产 者 :%d 等待中......\n", (clock() - StartTime) / CLOCKS_PER_SEC, *x);
		empty--;		   //等到一个空位置,先把他占用
		while (mutex <= 0) //P(mutex),判断当前临界区中是否有进程正在生产或者消费,如果有则等待并输出提示语句
			printf("%.4lfs|	缓冲区有进程正在操作 !	生产者 :%d 等待中......\n", (clock() - StartTime) / CLOCKS_PER_SEC, *x);
		mutex--;					  //等到占用进程出临界区,进入临界区并占用
		eletemp = (rand() % 26) + 65; //生产一个产品,即任意产生一个 A~Z 的字母
		//输出生产成功的字样
		printf("%.4lfs| 生产者: %d 生产一个产品: %c ,并将其放入缓冲区: %d 位置\n", (clock() - StartTime) / CLOCKS_PER_SEC, *x, eletemp, in);
		buffer[in] = eletemp; //将生产的产品存入缓冲区
		sum++;
		in = (in + 1) % N; //缓冲区索引更新
		mutex++;		   //V(mutex),临界区使用完成释放信号
		full++;			   //V(full),实现同步,释放 full
		sleep(SleepTime);  //当前生产者生产完成,休眠一定时间才能继续
	}
}

//消费者线程
void *Consumer(void *args)
{
	int *x = (int *)args;
	int sum = 0;
	while (sum < 10)
	{
		while (full <= 0) //P(full),判断缓冲区当中是否有产品可以消费,同步如果没有产品则等待同时输出提示语句
			printf("%.4lfs| \t\t\t\t\t\t\t 缓冲区为空!消费者: %d 等待中......\n", (clock() - StartTime) / CLOCKS_PER_SEC, *x);

		full--; //等到有一个产品,消费这个产品

		while (mutex <= 0) //P(mutex),判断临界区是否有进程在处理,如果有则等待并输出提示语句
			printf("%.4lfs| \t\t\t\t\t\t\t 缓冲区有进程正在操作! 消费者 : %d 等待中......\n", (clock() - StartTime) / CLOCKS_PER_SEC, *x);

		mutex--; //等到临界区为空,则进入缓冲区消费
		//输出消费成功的语句
		printf("%.4lfs| \t\t\t\t\t\t\t 消费者: %d 消费一个产品: %c , 产品位于位于缓冲区 : %d 位置 \n", (clock() - StartTime) / CLOCKS_PER_SEC, *x, buffer[out], out);
		buffer[out] = 0; //更新缓冲区,把消费掉的产品清空
		sum++;
		out = (out + 1) % N; //更新缓冲区索引
		mutex++;			 //消费完成退出缓冲区,释放资源
		empty++;			 //消费完成产生一个空位,进行同步
		sleep(SleepTime);	 //当前消费者消费了一个产品,休眠一段时间再继续
	}
}


int main()
{
	//记录程序开始的时间,方便记录消费者和生产者活动的时间
	StartTime = clock();
	//产生随机种子,生产的时候随机生产一个字符
	srand((int)time(NULL));
	printf(" 时间");
	printf("    \t\t 生产者动态显示");
	printf("          \t\t\t\t 消费者动态显示\n");
	printf("======================================================================================================================\n");
	//定义一个线程数组,存储所有的消费者和生产者线程
	pthread_t threadPool[ProNum + ConNum];

	int t1=1;
	int t2=2;
	pthread_create(&threadPool[0], NULL, Producer, (void *)&t1);
	pthread_create(&threadPool[1], NULL, Producer, (void *)&t2);
	pthread_create(&threadPool[2], NULL, Consumer, (void *)&t1);
	pthread_create(&threadPool[3], NULL, Consumer, (void *)&t2);

	void *result;
	for (int i = 0; i < ProNum + ConNum; i++) //等待线程结束并回收资源,线程之间同步
	{
		if (pthread_join(threadPool[i], &result) == -1)
		{
			printf("fail to recollect\n"); //进程回收,如果回收失败输出错误提示
			exit(1);					   //结束线程
		}
	}
}
  • 34
    点赞
  • 373
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
生产者消费者问题是一个经典的同步问题,其中生产者消费者共享一个缓冲区,生产者向缓冲区中生产产品,消费者从缓冲区中消费产品。在多线程的环境下,生产者消费者可能会同时访问缓冲区,因此需要对缓冲区进行同步控制。 以下是一个简单的生产者消费者问题的实现: ```c #include <stdio.h> #include <stdlib.h> #include <pthread.h> #define BUFFER_SIZE 10 int buffer[BUFFER_SIZE]; int count = 0; int in = 0; int out = 0; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t empty = PTHREAD_COND_INITIALIZER; pthread_cond_t full = PTHREAD_COND_INITIALIZER; void *producer(void *arg) { int i; for (i = 0; i < 20; i++) { pthread_mutex_lock(&mutex); while (count == BUFFER_SIZE) { pthread_cond_wait(&empty, &mutex); } buffer[in] = i; in = (in + 1) % BUFFER_SIZE; count++; printf("producer: produced %d\n", i); pthread_cond_signal(&full); pthread_mutex_unlock(&mutex); } pthread_exit(NULL); } void *consumer(void *arg) { int i, data; for (i = 0; i < 20; i++) { pthread_mutex_lock(&mutex); while (count == 0) { pthread_cond_wait(&full, &mutex); } data = buffer[out]; out = (out + 1) % BUFFER_SIZE; count--; printf("consumer: consumed %d\n", data); pthread_cond_signal(&empty); pthread_mutex_unlock(&mutex); } pthread_exit(NULL); } int main() { pthread_t tid1, tid2; pthread_create(&tid1, NULL, producer, NULL); pthread_create(&tid2, NULL, consumer, NULL); pthread_join(tid1, NULL); pthread_join(tid2, NULL); return 0; } ``` 在代码中,我们定义了一个大小为10的缓冲区,使用一个计数器count来记录缓冲区中产品的数量,in和out分别表示生产者消费者在缓冲区中的位置。我们使用了两个条件变量empty和full来控制生产者消费者的同步。 在生产者中,当缓冲区已满时,生产者会等待empty条件变量,直到缓冲区有空位。当生产者生产完一个产品后,会唤醒消费者,并释放互斥锁。 在消费者中,当缓冲区为空时,消费者会等待full条件变量,直到缓冲区有产品。当消费者消费完一个产品后,会唤醒生产者,并释放互斥锁。 通过使用互斥锁和条件变量,我们可以保证生产者消费者的正确同步,避免了竞争条件和死锁等问题

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值