《Linux系统编程篇》System V信号量实现生产者与消费者问题(Linux 进程间通信(IPC))——基础篇(拓展思维)

文章目录

      • 📚 **生产者-消费者问题**
      • 🔑 **问题分析**
      • 🛠️ **详细实现:生产者-消费者**
        • **步骤 1:定义信号量和缓冲区**
        • **步骤 2:创建信号量**
        • **步骤 3:生产者进程**
        • **步骤 4:消费者进程**
        • **步骤 5:创建进程并启动**
      • 🧑‍🔧 **完整代码示例**
      • 🎯 **关键点总结**

接上节,我们来详细展开一下 生产者-消费者问题,并用 System V 信号量 来解决它。这个经典问题帮助我们理解如何在多个进程间同步和互斥地共享资源。

📚 生产者-消费者问题

生产者-消费者问题是多进程同步问题中的经典例子。问题的背景是:有两个进程,一个生产者(Producer)不断生产产品,另一个消费者(Consumer)不断消费产品。两者都需要共享一个有限的缓冲区。生产者往缓冲区写入数据,消费者从缓冲区读取数据。为了避免并发问题,我们需要同步生产者和消费者的访问。

具体的挑战是:

  1. 互斥:生产者和消费者在访问共享缓冲区时,不能同时操作。
  2. 同步:缓冲区不能超过最大容量,也不能为空。

🔑 问题分析

我们需要使用信号量来解决这些问题,具体来说,我们需要:

  1. 一个信号量来控制缓冲区的空位置数(空位信号量)。
  2. 一个信号量来控制缓冲区的已满位置数(已满信号量)。
  3. 一个互斥信号量来保证每次只有一个进程(生产者或消费者)可以访问缓冲区。

我们通过信号量来控制:

  • 当缓冲区为空时,消费者应该等待。
  • 当缓冲区已满时,生产者应该等待。
  • 互斥信号量保证在访问共享缓冲区时,只有一个进程能够进入临界区。

🛠️ 详细实现:生产者-消费者

步骤 1:定义信号量和缓冲区

我们将使用以下信号量:

  • empty:缓冲区中空位的数量,初始值为 BUFFER_SIZE
  • full:缓冲区中已满的数量,初始值为 0
  • mutex:互斥锁,用来确保每次只有一个进程能够访问缓冲区,初始值为 1

缓冲区本身可以用一个数组来表示:

#define BUFFER_SIZE 5  // 缓冲区大小
#define NUM_ITEMS 10   // 生产和消费的物品数量int buffer[BUFFER_SIZE];  // 缓冲区
int in = 0;  // 指向下一个要写入的位置
int out = 0; // 指向下一个要读取的位置
步骤 2:创建信号量

我们通过 semget() 创建信号量集:

int sem_id = semget(IPC_PRIVATE, 3, IPC_CREAT | 0666);  // 创建3个信号量
if (sem_id == -1) {perror("semget");exit(1);
}// 初始化信号量
semctl(sem_id, 0, SETVAL, BUFFER_SIZE);  // empty 信号量:初始为缓冲区大小
semctl(sem_id, 1, SETVAL, 0);  // full 信号量:初始为0,表示缓冲区没有物品
semctl(sem_id, 2, SETVAL, 1);  // mutex 信号量:初始为1,表示可以访问缓冲区
步骤 3:生产者进程

生产者进程的工作流程如下:

  1. 等待空位信号量(empty:只有在有空位时才能生产。
  2. 获取互斥信号量(mutex:进入临界区,确保没有其他进程操作缓冲区。
  3. 生产:将数据放入缓冲区。
  4. 释放互斥信号量(mutex:退出临界区。
  5. 增加已满信号量(full:表明缓冲区中有一个新产品,消费者可以消费。

生产者代码示例:

void producer(int sem_id) {for (int i = 0; i < NUM_ITEMS; i++) {struct sembuf sops[2];// P(empty)sops[0].sem_num = 0;sops[0].sem_op = -1;sops[0].sem_flg = 0;// P(mutex)sops[1].sem_num = 2;sops[1].sem_op = -1;sops[1].sem_flg = 0;semop(sem_id, sops, 2);buffer[in] = i;printf("生产者生产了产品 %d\n", i);in = (in + 1) % BUFFER_SIZE;// V(mutex) 和 V(full)struct sembuf sops_release[2];sops_release[0].sem_num = 2; // mutexsops_release[0].sem_op = 1;sops_release[0].sem_flg = 0;sops_release[1].sem_num = 1; // fullsops_release[1].sem_op = 1;sops_release[1].sem_flg = 0;semop(sem_id, sops_release, 2);sleep(1);}
}
步骤 4:消费者进程

消费者进程的工作流程如下:

  1. 等待已满信号量(full:只有在缓冲区有物品时才能消费。
  2. 获取互斥信号量(mutex:进入临界区,确保没有其他进程操作缓冲区。
  3. 消费:从缓冲区中取出数据。
  4. 释放互斥信号量(mutex:退出临界区。
  5. 增加空位信号量(empty:表明缓冲区有一个空位,生产者可以生产。

消费者代码示例:

void consumer(int sem_id) {for (int i = 0; i < NUM_ITEMS; i++) {struct sembuf sops[2];// P(full)sops[0].sem_num = 1;sops[0].sem_op = -1;sops[0].sem_flg = 0;// P(mutex)sops[1].sem_num = 2;sops[1].sem_op = -1;sops[1].sem_flg = 0;semop(sem_id, sops, 2);int item = buffer[out];printf("消费者消费了产品 %d\n", item);out = (out + 1) % BUFFER_SIZE;// V(mutex) 和 V(empty)struct sembuf sops_release[2];sops_release[0].sem_num = 2; // mutexsops_release[0].sem_op = 1;sops_release[0].sem_flg = 0;sops_release[1].sem_num = 0; // emptysops_release[1].sem_op = 1;sops_release[1].sem_flg = 0;semop(sem_id, sops_release, 2);sleep(1);}
}

步骤 5:创建进程并启动

main() 函数中,我们创建了一个子进程,用于运行消费者进程。父进程将作为生产者运行。

int main() {int sem_id = semget(IPC_PRIVATE, 3, IPC_CREAT | 0666);if (sem_id == -1) {perror("semget");exit(1);}semctl(sem_id, 0, SETVAL, BUFFER_SIZE); // emptysemctl(sem_id, 1, SETVAL, 0);           // fullsemctl(sem_id, 2, SETVAL, 1);           // mutexpid_t pid = fork();if (pid == 0) {consumer(sem_id);} else if (pid > 0) {producer(sem_id);wait(NULL);semctl(sem_id, 0, IPC_RMID);} else {perror("fork failed");exit(1);}return 0;
}

在这段代码中:

  • fork():通过 fork() 创建一个新的子进程。父进程作为生产者执行 producer() 函数,子进程作为消费者执行 consumer() 函数。
  • 父进程和子进程分工:生产者不断生产物品放入缓冲区,消费者从缓冲区取出物品进行消费。
  • wait(NULL):父进程使用 wait() 来等待子进程的结束,这样可以确保父进程在子进程完成后再退出,避免资源的提前释放。
  • 删除信号量集:为了避免信号量集泄露,程序结束时通过 semctl() 删除创建的信号量集。

🧑‍🔧 完整代码示例

这里是完整的代码,包含了生产者和消费者进程的实现,以及使用 System V 信号量同步和互斥访问共享缓冲区。

#include <stdio.h>
#include <stdlib.h>
#include <sys/sem.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>#define BUFFER_SIZE 5
#define NUM_ITEMS 10int buffer[BUFFER_SIZE];
int in = 0;
int out = 0;void producer(int sem_id) {for (int i = 0; i < NUM_ITEMS; i++) {struct sembuf sops[2];// P(empty)sops[0].sem_num = 0;sops[0].sem_op = -1;sops[0].sem_flg = 0;// P(mutex)sops[1].sem_num = 2;sops[1].sem_op = -1;sops[1].sem_flg = 0;semop(sem_id, sops, 2);buffer[in] = i;printf("生产者生产了产品 %d\n", i);in = (in + 1) % BUFFER_SIZE;// V(mutex) 和 V(full)struct sembuf sops_release[2];sops_release[0].sem_num = 2; // mutexsops_release[0].sem_op = 1;sops_release[0].sem_flg = 0;sops_release[1].sem_num = 1; // fullsops_release[1].sem_op = 1;sops_release[1].sem_flg = 0;semop(sem_id, sops_release, 2);sleep(1);}
}void consumer(int sem_id) {for (int i = 0; i < NUM_ITEMS; i++) {struct sembuf sops[2];// P(full)sops[0].sem_num = 1;sops[0].sem_op = -1;sops[0].sem_flg = 0;// P(mutex)sops[1].sem_num = 2;sops[1].sem_op = -1;sops[1].sem_flg = 0;semop(sem_id, sops, 2);int item = buffer[out];printf("消费者消费了产品 %d\n", item);out = (out + 1) % BUFFER_SIZE;// V(mutex) 和 V(empty)struct sembuf sops_release[2];sops_release[0].sem_num = 2; // mutexsops_release[0].sem_op = 1;sops_release[0].sem_flg = 0;sops_release[1].sem_num = 0; // emptysops_release[1].sem_op = 1;sops_release[1].sem_flg = 0;semop(sem_id, sops_release, 2);sleep(1);}
}int main() {int sem_id = semget(IPC_PRIVATE, 3, IPC_CREAT | 0666);if (sem_id == -1) {perror("semget");exit(1);}semctl(sem_id, 0, SETVAL, BUFFER_SIZE); // emptysemctl(sem_id, 1, SETVAL, 0);           // fullsemctl(sem_id, 2, SETVAL, 1);           // mutexpid_t pid = fork();if (pid == 0) {consumer(sem_id);} else if (pid > 0) {producer(sem_id);wait(NULL);semctl(sem_id, 0, IPC_RMID);} else {perror("fork failed");exit(1);}return 0;
}

🎯 关键点总结

  1. 信号量的使用:通过 emptyfullmutex 信号量实现生产者和消费者的同步与互斥。
  2. semop() 调用:每次生产者或消费者对共享资源进行操作时,都需要通过 semop() 来执行信号量操作,确保数据的正确访问顺序。
  3. P()V() 操作:通过 P() 操作来阻塞等待资源,V() 操作来释放资源,确保进程按预期顺序执行。

通过这个实例,你可以更加深入地理解如何使用 System V 信号量 来解决实际的同步和互斥问题。在实际应用中,生产者消费者模式广泛应用于操作系统调度、缓冲区管理等场景。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/70960.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

利用 Python 爬虫进行跨境电商数据采集

1 引言2 代理IP的优势3 获取代理IP账号4 爬取实战案例---&#xff08;某电商网站爬取&#xff09;4.1 网站分析4.2 编写代码4.3 优化代码 5 总结 1 引言 在数字化时代&#xff0c;数据作为核心资源蕴含重要价值&#xff0c;网络爬虫成为企业洞察市场趋势、学术研究探索未知领域…

HONOR荣耀MagicBook 15 2021款 独显(BOD-WXX9,BDR-WFH9HN)原厂Win10系统

适用型号&#xff1a;【BOD-WXX9】 MagicBook 15 2021款 i7 独显 MX450 16GB512GB (BDR-WFE9HN) MagicBook 15 2021款 i5 独显 MX450 16GB512GB (BDR-WFH9HN) MagicBook 15 2021款 i5 集显 16GB512GB (BDR-WFH9HN) 链接&#xff1a;https://pan.baidu.com/s/1S6L57ADS18fnJZ1…

c语言实现三子棋小游戏(涉及二维数组、函数、循环、常量、动态取地址等知识点)

使用C语言实现一个三子棋小游戏 涉及知识点&#xff1a;二维数组、自定义函数、自带函数库、循环、常量、动态取地址等等 一些细节点&#xff1a; 1、引入自定义头文件&#xff0c;需要用""双引号包裹文件名&#xff0c;目的是为了和官方头文件的<>区分开。…

C语言数据类型及其使用 (带示例)

目录 1. 基本数据类型 整型 浮点型 字符型 2. 构造数据类型 数组 结构体 联合体&#xff08;共用体&#xff09; 枚举类型 3. 指针类型 4. 空类型 在 C 语言中&#xff0c;数据类型是非常重要的概念&#xff0c;它决定了数据在内存中的存储方式、占用空间大小以及可…

Web自动化之Selenium添加网站Cookies实现免登录

在使用Selenium进行Web自动化时&#xff0c;添加网站Cookies是实现免登录的一种高效方法。通过模拟浏览器行为&#xff0c;我们可以将已登录状态的Cookies存储起来&#xff0c;并在下次自动化测试或爬虫任务中直接加载这些Cookies&#xff0c;从而跳过登录步骤。 Cookies简介 …

NAT 技术:网络中的 “地址魔术师”

目录 一、性能瓶颈&#xff1a;NAT 的 “阿喀琉斯之踵” &#xff08;一&#xff09;数据包处理延迟 &#xff08;二&#xff09;高并发下的性能损耗 二、应用兼容性&#xff1a;NAT 带来的 “适配难题” &#xff08;一&#xff09;端到端通信的困境 &#xff08;二&…

php序列化与反序列化

文章目录 基础知识魔术方法&#xff1a;在序列化和反序列化过程中自动调用的方法什么是 __destruct() 方法&#xff1f;何时触发 __destruct() 方法&#xff1f;用途&#xff1a;语法示例&#xff1a; 反序列化漏洞利用前提条件一些绕过策略绕过__wakeup函数绕过正则匹配绕过相…

docker 占用系统空间太大了,整体迁移到挂载的其他磁盘|【当前普通用户使用docker时,无法指定镜像、容器安装位置【无法指定】】

文章目录 前言【核心步骤皆为 大模型生成的方案】总结步骤应该是&#xff1a;详细步骤如下1. **停止 Docker 服务**2. **备份原数据&#xff08;防止迁移失败&#xff09;**3. **迁移数据到新磁盘**4. **修改 Docker 配置文件**5. **重启 Docker 服务**6. **验证容器和镜像**7.…

设计后端返回给前端的返回体

目录 1、为什么要设计返回体&#xff1f; 2、返回体包含哪些内容&#xff08;如何设计&#xff09;&#xff1f; 举例 3、总结 1、为什么要设计返回体&#xff1f; 在设计后端返回给前端的返回体时&#xff0c;通常需要遵循一定的规范&#xff0c;以确保前后端交互的清晰性…

Springboot 自动化装配的原理

Springboot 自动化装配的原理 SpringBoot 主要作用为&#xff1a;起步依赖、自动装配。而为了实现这种功能&#xff0c;SpringBoot 底层主要使用了 SpringBootApplication 注解。 首先&#xff0c;SpringBootApplication 是一个复合注解&#xff0c;它结合了 Configuration、…

基于vue框架的游戏博客网站设计iw282(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;用户,博客信息,资源共享,游戏视频,游戏照片 开题报告内容 基于FlaskVue框架的游戏博客网站设计开题报告 一、项目背景与意义 随着互联网技术的飞速发展和游戏产业的不断壮大&#xff0c;游戏玩家对游戏资讯、攻略、评测等内容的需求日…

算法-二叉树篇13-路径总和

路径总和 力扣题目链接 题目描述 给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径&#xff0c;这条路径上所有节点值相加等于目标和 targetSum 。如果存在&#xff0c;返回 true &#xff1b;否则&#xff0c;返回…

8. 示例:对32位数据总线实现位宽和值域覆盖

文章目录 前言示例一&#xff1a;示例二&#xff1a;示例三&#xff1a;仿真与覆盖率分析覆盖点详细说明覆盖率提升技巧常见错误排查 示例四&#xff1a;仿真步骤 前言 针对32位数据总线实现位宽和值域的覆盖&#xff0c;并且能够用xrun运行&#xff0c;查看日志和波形。cover…

TDengine 中的数据库

数据库概念 时序数据库 TDengine 中数据库概念&#xff0c;等同于关系型数据库 MYSQL PostgreSQL 中的数据库&#xff0c;都是对资源进行分割管理的单位。 TDengine 数据库与关系型数据库最大区别是跨库操作&#xff0c;TDengine 数据库跨库操作除了少量几个SQL 能支持外&…

开源电商项目、物联网项目、销售系统项目和社区团购项目

以下是推荐的开源电商项目、物联网项目、销售系统项目和社区团购项目&#xff0c;均使用Java开发&#xff0c;且无需付费&#xff0c;GitHub地址如下&#xff1a; ### 开源电商项目 1. **mall** GitHub地址&#xff1a;[https://github.com/macrozheng/mall](https://git…

如何设计一个短链系统?

短链系统设计的关键要点: 系统功能实现 短链生成:接收长链接,先检查是否已有对应短链,存在则直接返回。否则,使用分布式 ID 生成器(如号段模式、SnowFlake 算法、数据库自增 ID、Redis 自增等)生成唯一 ID,或通过哈希算法(如 MurmurHash)处理长链接得到哈希值。再将生…

数据结构(初阶)(三)----单链表

单链表 概念 概念&#xff1a;链表是⼀种物理存储结构上⾮连续、⾮顺序的存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的指针链接次序实现的。 结点 与顺序表不同的是&#xff0c;链表的结构类似于带车头的火车车厢&#xff0c;&#xff0c;链表的每个车厢都是独立…

游戏引擎学习第129天

仓库:https://gitee.com/mrxiao_com/2d_game_3 小妙招: vscode:定位错误行 一顿狂按F8 重构快捷键:F2 重构相关的变量 回顾并为今天的内容做准备 今天的工作主要集中在渲染器的改进上&#xff0c;渲染器现在运行得相当不错&#xff0c;得益于一些优化和组织上的改进。我们计…

文字描边实现内黄外绿效果

网页使用 <!DOCTYPE html> <html> <head> <style> .text-effect {color: #ffd700; /* 黄色文字 */-webkit-text-stroke: 2px #008000; /* 绿色描边&#xff08;兼容Webkit内核&#xff09; */text-stroke: 2px #008000; /* 标准语法 *…

yolov8 目标追踪 (源码 +效果图)

1.在代码中 增加了s键开始追踪 e键结束追踪 显示移动距离(代码中可调标尺和像素的比值 以便接近实际距离) 2.绘制了监测区域 只在区域内的检测 3.规定了检测的类别 只有人类才绘制轨迹 import osimport cv2 from ultralytics import YOLO from collections import defaultdic…