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

article/2025/11/10 0:41:56

1. 任务简介

生产者消费者问题(Producer-consumer problem),也称有限缓冲问题(Bounded-buffer problem),是一个著名的进程同步问题的经典案例。它描述的是有一组生产者进程在生产产品,并将这些产品提供给一组消费者进程去消费。为使生产者进程和消费者进程能够并发执行,在这两者之间设置里一个具有 n n n个缓冲区的缓冲池,生产者进程将他所生产的的产品放入一个缓冲区中;消费者进程可从一个缓冲区中取走产品并进行消费。尽管所有的生产者进程和消费者进程都是以异步方式运行的,但亡们之间必须保特同步,即不允许消费者进程到一个空缓冲区中去取产品;也不允许生产者进程向一个已装满产品且产品尚未被取走的缓冲区投放产品。

本项目要求利用Linux多进程实现生产者消费者问题。

2. 思路分析

我们分析题目中的同步和互斥关系:

2.1 同步关系

  • 当缓冲区有空位时,生产者进程才可以生产
  • 当缓冲区有产品是,消费者进程才可以消费

2.2 互斥关系

  • 生产者进程与消费者进程对缓冲区的访问是互斥的

在这里插入图片描述

2.3 整体思路

总体思路如下:

  • 设置一个生产者进程,负责生产产品
  • 设置一个消费这进程,负责消费产品
  • 生产者与消费者进程间的通讯通过共享内存实现
  • 设置一个互斥信号量,实现对共享内存的互斥访问
  • 设置两个信号量,用于标记资源的数目,实现进程间的两个同步关系

具体流程如下图所示:

在这里插入图片描述

3. 代码实现

3.1 头文件

首先,我们包含实现问题所需的头文件:

#include <time.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/ipc.h>

3.2 预定义和数据结构

  • 我们采用共同协商关键字SEMKEYSHMKEY的方法使得不同进程间可以取得同一个信号量和共享内存
  • 定义了一个结构体Buffer来作为缓冲池存储产品
#define SEMKEY 123
#define SHMKEY 456
#define BUFNUM 10
#define SEMNUM 3#if defined(__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED)
/*   union   semun   is   defined   by   including   <sys/sem.h>   */ 
#else 
/*   according   to   X/OPEN   we   have   to   define   it   ourselves   */ 
union semun
{int val;struct semid_ds *buf;unsigned short *array;
};
#endifstruct Buffer
{int start, end;char buffer[BUFNUM];
};

3.3 初始化函数

  • 我们利用协商好的SEMKEY生成一个信号量集,其中第一个信号量为empty,表示缓冲池为空的个数;第二个信号量为full,表示缓冲池中的产品个数;第三个信号量为mutex,控制对缓冲池的读取权限。
  • 利用协商好的SHMKEY生成一个共享内存集
  • 我们利用*returnSemId*returnShmId**returnShm三个指针来返回初始化的参数

具体实现如下:

void Initialize(int *returnSemId, int *returnShmId, struct Buffer **returnShm)
{int semId = -1, shmId = -1, values[SEMNUM] = {BUFNUM, 0, 1};/*  semSet[0]: empty, initial value: nsemSet[1]: full, initial value 0semSet[2]: mutex, initial value 1   */semId = semget(SEMKEY, SEMNUM, IPC_CREAT | 0666);if(semId == -1){printf("semaphore creation failed!\n");exit(EXIT_FAILURE);}int i = 0;union semun semUn;for( i = 0; i < SEMNUM; i ++){semUn.val = values[i];if(semctl(semId, i, SETVAL, semUn) < 0){printf("semaphore %d initialization failed!\n", i);exit(EXIT_FAILURE);}}shmId = shmget(SHMKEY, sizeof(struct Buffer), IPC_CREAT | 0666);if(shmId == -1){printf("share memory creation failed!\n");exit(EXIT_FAILURE);}void *temp = NULL;struct Buffer *shm = NULL;temp = shmat(shmId, 0, 0);if(temp == (void *) -1){printf("share memory attachment failed!\n");exit(EXIT_FAILURE);        }shm = (struct Buffer *) temp;shm -> start = 0;shm -> end = 0;for(i = 0; i < BUFNUM; i++){shm -> buffer[i] = ' ';}*returnSemId = semId;*returnShmId = shmId;*returnShm = shm;
}

3.4 PV操作

给定信号量集的semId以及待操作的信号量下标semNum,其P操作和V如下所示:

void SemWait(int semId, int semNum)
{struct sembuf semBuf;semBuf.sem_num = semNum;semBuf.sem_op = -1;semBuf.sem_flg = SEM_UNDO;if(semop(semId, &semBuf, 1) == -1){printf("semaphore P operation failed!\n");exit(EXIT_FAILURE);}
}void SemSignal(int semId, int semNum)
{struct sembuf semBuf;semBuf.sem_num = semNum;semBuf.sem_op = 1;semBuf.sem_flg = SEM_UNDO;if(semop(semId, &semBuf, 1) == -1){printf("semaphore V operation failed!\n");exit(EXIT_FAILURE);}
}

3.5 生产者进程

生产者首先申请一个空闲缓冲区资源,再申请临界缓冲区访问。当产生一个产品后,发送一个信号,使得已有缓冲区资源数量加一,同时唤醒阻塞的消费者进程,具体代码如下:

void Producer(int semId, struct Buffer *shm)
{do{// wait empty regionSemWait(semId, 0);// wait mutexSemWait(semId, 2);Add(shm);// signal mutexSemSignal(semId, 2);// singal full regionSemSignal(semId, 1);sleep(random() % 2);}while(1);
}

执行Add操作时,随机产生一个大写英文字母模拟产品,放入缓冲区,同时调整队尾指针end,具体代码如下:

void Add(struct Buffer *shm)
{char product = 'A' + rand() % 26;printf("producer %d: added product %c into buffer:\t", getpid(), product);shm -> buffer [shm -> end] = product;shm -> end = (shm -> end + 1) % BUFNUM;printf("|%s|\n", shm -> buffer);
}

3.6 消费者进程

消费者首先申请一个已有缓冲区资源,再申请临界缓冲区访问。当消费一个产品后,发送一个信号,使得空闲缓冲区资源数量加一,同时唤醒阻塞的生产者进程,具体代码如下:

void Producer(int semId, struct Buffer *shm)
{do{// wait empty regionSemWait(semId, 0);// wait mutexSemWait(semId, 2);Add(shm);// signal mutexSemSignal(semId, 2);// singal full regionSemSignal(semId, 1);sleep(random() % 2);}while(1);
}

执行Remove操作时,将当前缓冲区资源清空,同时调整队首指针start,具体代码如下:

void Remove(struct Buffer *shm)
{char product = shm -> buffer [shm -> start];printf("consumer %d: removed product %c from buffer:\t", getpid(), product);shm -> buffer [shm -> start] = ' ';shm -> start = (shm -> start + 1) % BUFNUM;printf("|%s|\n", shm -> buffer);
}

3.7 主函数

从控制台通过-n命令读入产生生产者和消费者进程的数目,首先初始化变量,之后通过fork()产生等量的生产者和消费者进程。

注意:此处主进程在产生完其他子进程之后不能够直接退出,否则子进程会修改父进程为systemd,试的我们无法通过控制台的ctrl + c命令结束程序。

int main(int argc, char *argv[])
{int semId = -1, shmId = -1, i=0;int processNum = atoi(argv[2]);if(processNum <= 0) processNum = 1;struct Buffer *shm = NULL;Initialize(&semId, &shmId, &shm);for(i = 0; i < 2 * processNum; i ++){pid_t pid = fork();if(pid < 0){printf("fork failed!\n");exit(EXIT_FAILURE);}else if(pid == 0){sleep(1);if(i % 2 == 0){printf("producer process %d created\n", getpid());Producer(semId, shm);            }else{printf("consumer process %d created\n", getpid());Consumer(semId, shm);}return 0;}}getchar();Destroy(semId, shmId, shm);return 0;
}

3.8 实验代码

完整实验代码如下:

#include <time.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/ipc.h>#define SEMKEY 123
#define SHMKEY 456
#define BUFNUM 10
#define SEMNUM 3#if defined(__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED)
/*   union   semun   is   defined   by   including   <sys/sem.h>   */ 
#else 
/*   according   to   X/OPEN   we   have   to   define   it   ourselves   */ 
union semun
{int val;struct semid_ds *buf;unsigned short *array;
};
#endifstruct Buffer
{int start, end;char buffer[BUFNUM];
};void Initialize(int *returnSemId, int *returnShmId, struct Buffer **returnShm)
{int semId = -1, shmId = -1, values[SEMNUM] = {BUFNUM, 0, 1};/*  semSet[0]: empty, initial value: nsemSet[1]: full, initial value 0semSet[2]: mutex, initial value 1   */semId = semget(SEMKEY, SEMNUM, IPC_CREAT | 0666);if(semId == -1){printf("semaphore creation failed!\n");exit(EXIT_FAILURE);}int i = 0;union semun semUn;for(i = 0; i < SEMNUM; i ++){semUn.val = values[i];if(semctl(semId, i, SETVAL, semUn) < 0){printf("semaphore %d initialization failed!\n", i);exit(EXIT_FAILURE);}}shmId = shmget(SHMKEY, sizeof(struct Buffer), IPC_CREAT | 0666);if(shmId == -1){printf("share memory creation failed!\n");exit(EXIT_FAILURE);}void *temp = NULL;struct Buffer *shm = NULL;temp = shmat(shmId, 0, 0);if(temp == (void *) -1){printf("share memory attachment failed!\n");exit(EXIT_FAILURE);        }shm = (struct Buffer *) temp;shm -> start = 0;shm -> end = 0;for(i = 0; i < BUFNUM; i++){shm -> buffer[i] = ' ';}*returnSemId = semId;*returnShmId = shmId;*returnShm = shm;
}void Add(struct Buffer *shm)
{char product = 'A' + rand() % 26;printf("producer %d: added product %c into buffer:\t", getpid(), product);shm -> buffer [shm -> end] = product;shm -> end = (shm -> end + 1) % BUFNUM;printf("|%s|\n", shm -> buffer);
}void Remove(struct Buffer *shm)
{char product = shm -> buffer [shm -> start];printf("consumer %d: removed product %c from buffer:\t", getpid(), product);shm -> buffer [shm -> start] = ' ';shm -> start = (shm -> start + 1) % BUFNUM;printf("|%s|\n", shm -> buffer);
}void ShmDestroy(int semId, struct Buffer * shm)
{if(shmdt(shm) < 0){printf("share memory detachment failed!\n");exit(EXIT_FAILURE);} if(shmctl(semId, IPC_RMID, 0) < 0){printf("share memory destruction failed!\n");exit(EXIT_FAILURE);        }
}void SemWait(int semId, int semNum)
{struct sembuf semBuf;semBuf.sem_num = semNum;semBuf.sem_op = -1;semBuf.sem_flg = SEM_UNDO;if(semop(semId, &semBuf, 1) == -1){printf("semaphore P operation failed!\n");exit(EXIT_FAILURE);}
}void SemSignal(int semId, int semNum)
{struct sembuf semBuf;semBuf.sem_num = semNum;semBuf.sem_op = 1;semBuf.sem_flg = SEM_UNDO;if(semop(semId, &semBuf, 1) == -1){printf("semaphore V operation failed!\n");exit(EXIT_FAILURE);}
}void SemDestroy(int semId)
{union semun semUn;if(semctl(semId, 0, IPC_RMID, semUn) < 0){printf("semaphore destruction failed!\n");exit(EXIT_FAILURE);}
}void Destroy(int semId, int shmId, struct Buffer *shm)
{SemDestroy(semId);ShmDestroy(shmId, shm);printf("destruction finished! exit\n");
}void Producer(int semId, struct Buffer *shm)
{do{// wait empty regionSemWait(semId, 0);// wait mutexSemWait(semId, 2);Add(shm);// signal mutexSemSignal(semId, 2);// singal full regionSemSignal(semId, 1);sleep(random() % 2);}while(1);
}void Consumer(int semId, struct Buffer *shm)
{do{// wait full regionSemWait(semId, 1);// wait mutexSemWait(semId, 2);Remove(shm);// signal mutexSemSignal(semId, 2);// singal empty regionSemSignal(semId, 0);sleep(random() % 2);}while(1);
}int main(int argc, char *argv[])
{int semId = -1, shmId = -1, i=0;int processNum = atoi(argv[2]);if(processNum <= 0) processNum = 1;struct Buffer *shm = NULL;Initialize(&semId, &shmId, &shm);for(i = 0; i < 2 * processNum; i ++){pid_t pid = fork();if(pid < 0){printf("fork failed!\n");exit(EXIT_FAILURE);}else if(pid == 0){sleep(1);if(i % 2 == 0){printf("producer process %d created\n", getpid());Producer(semId, shm);            }else{printf("consumer process %d created\n", getpid());Consumer(semId, shm);}return 0;}}getchar();Destroy(semId, shmId, shm);return 0;
}

4. 实验结果

我们通过gcc编译器编译源程序producer_consumer.c,生成目标文件producer_consumer

在这里插入图片描述

4.1 单个生产者消费者

我们从控制台输入命令$ ./producer_consumer -n 1,来模拟一个生产者和一个消费者的情况:

在这里插入图片描述

我们可以清楚的看到一个生产者一个消费者进程存在时的状况。

4.2 多个生产者消费者

同理,我们从控制台输入命令$ ./producer_consumer -n 5,来模拟多个生产者和多个消费者的情况,实验结果节选片段如下:

在这里插入图片描述

我们可以很轻易的通过上图所示的缓冲池可视化结果,验证我们程序的正确性,至此实验部分介绍完毕。


http://chatgpt.dhexx.cn/article/8N1D99yD.shtml

相关文章

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

生产者—消费者问题 生产者—消费者题型在各类考试&#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…

IDEA自动生成代码插件

官方介绍 基于IntelliJ IDEA开发的代码生成插件&#xff0c;支持自定义任意模板&#xff08;Java&#xff0c;html&#xff0c;js&#xff0c;xml&#xff09;。 只要是与数据库相关的代码都可以通过自定义模板来生成。支持数据库类型与java类型映射关系配置。 支持同时生成生…