linux网络编程之System V 信号量(三):基于生产者-消费者模型实现先进先出的共享内存段

本文主要是介绍linux网络编程之System V 信号量(三):基于生产者-消费者模型实现先进先出的共享内存段,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

生产者消费者问题:该问题描述了两个共享固定大小缓冲区的进程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。
我们可以用信号量解决生产者消费者问题,如下图:
这里写图片描述
定义3个信号量,sem_full 和 sem_empty 用于生产者进程和消费者进程之间同步,即缓冲区为空才能生产,缓冲区不为空才能消费。由于共享同一块缓冲区,在生产一个产品过程中不能生产/消费产品,在消费一个产品的过程中不能生产/消费产品,故再使用一个 sem_mutex 信号量来约束行为,即进程间互斥。

下面基于生产者消费者模型,来实现一个先进先出的共享内存段:
这里写图片描述
如上图所示,定义两个结构体,shmhead 是共享内存段的头部,保存了块大小,块数,读写索引。shmfifo 保存了共享内存头部的指针,有效负载的起始地址,创建的共享内存段的shmid,以及3个信号量集的semid。
下面来封装几个函数:

#include "shmfifo.h"
#include <assert.h>shmfifo_t *shmfifo_init(int key, int blksize, int blocks)
{shmfifo_t *fifo = (shmfifo_t *)malloc(sizeof(shmfifo_t));assert(fifo != NULL);memset(fifo, 0, sizeof(shmfifo_t));int shmid;shmid = shmget(key, 0, 0);int size = sizeof(shmhead_t) + blksize * blocks;if (shmid == -1){fifo->shmid = shmget(key, size, IPC_CREAT | 0666);if (fifo->shmid == -1)ERR_EXIT("shmget");fifo->p_shm = (shmhead_t *)shmat(fifo->shmid, NULL, 0);if (fifo->p_shm == (shmhead_t *) - 1)ERR_EXIT("shmat");fifo->p_payload = (char *)(fifo->p_shm + 1);fifo->p_shm->blksize = blksize;fifo->p_shm->blocks = blocks;fifo->p_shm->rd_index = 0;fifo->p_shm->wr_index = 0;fifo->sem_mutex = sem_create(key);fifo->sem_full = sem_create(key + 1);fifo->sem_empty = sem_create(key + 2);sem_setval(fifo->sem_mutex, 1);sem_setval(fifo->sem_full, blocks);sem_setval(fifo->sem_empty, 0);}else{fifo->shmid = shmid;fifo->p_shm = (shmhead_t *)shmat(fifo->shmid, NULL, 0);if (fifo->p_shm == (shmhead_t *) - 1)ERR_EXIT("shmat");fifo->p_payload = (char *)(fifo->p_shm + 1);fifo->sem_mutex = sem_open(key);fifo->sem_full = sem_open(key + 1);fifo->sem_empty = sem_open(key + 2);}return fifo;
}void shmfifo_put(shmfifo_t *fifo, const void *buf)
{sem_p(fifo->sem_full);sem_p(fifo->sem_mutex);memcpy(fifo->p_payload + fifo->p_shm->blksize * fifo->p_shm->wr_index,buf, fifo->p_shm->blksize);fifo->p_shm->wr_index = (fifo->p_shm->wr_index + 1) % fifo->p_shm->blocks;sem_v(fifo->sem_mutex);sem_v(fifo->sem_empty);
}void shmfifo_get(shmfifo_t *fifo, void *buf)
{sem_p(fifo->sem_empty);sem_p(fifo->sem_mutex);memcpy(buf, fifo->p_payload + fifo->p_shm->blksize * fifo->p_shm->rd_index,fifo->p_shm->blksize);fifo->p_shm->rd_index = (fifo->p_shm->rd_index + 1) % fifo->p_shm->blocks;sem_v(fifo->sem_mutex);sem_v(fifo->sem_full);
}void shmfifo_destroy(shmfifo_t *fifo)
{sem_d(fifo->sem_mutex);sem_d(fifo->sem_full);sem_d(fifo->sem_empty);shmdt(fifo->p_shm);shmctl(fifo->shmid, IPC_RMID, 0);free(fifo);
}

1、shmfifo_init:先分配shmfifo 结构体的内存,如果尝试打开共享内存失败则创建,创建的共享内存段大小 = shmhead大小 + 块大小×块数目,然后shmat将此共享内存段映射到进程地址空间,然后使用sem_create 创建3个信号量集,每个信号集只有一个信号量,即上面提到的3个信号量,设置每个信号量的资源初始值。如果共享内存已经存在,则直接shmat映射下即可,此时3个信号量集也已经存在,sem_open 打开即可。sem_xxx 系列封装函数参考这里。
2、shmfifo_put:参照第一个生产者消费者的图,除去sem_p,sem_v 操作之外,中间就将buf 的内容memcpy 到对应缓冲区块,然后移动wr_index。
3、shmfifo_get:与shmfifo_put 类似,执行的是相反的操作。
4、shmfifo_destroy:删除3个信号量集,将共享内存段从进程地址空间剥离,删除共享内存段,释放shmfifo 结构体的内存。

下面是生产者程序和消费者程序:
shmfifo_send.c

#include "shmfifo.h"typedef struct stu
{char name[32];int age;
} STU;
int main(void)
{shmfifo_t *fifo = shmfifo_init(1234, sizeof(STU), 3);STU s;memset(&s, 0, sizeof(STU));s.name[0] = 'A';int i;for (i = 0; i < 5; i++){s.age = 20 + i;shmfifo_put(fifo, &s);s.name[0] = s.name[0] + 1;printf("send ok\n");}free(fifo);return 0;
}

shmfifo_recv.c

#include "shmfifo.h"typedef struct stu
{char name[32];int age;
} STU;int main(void)
{shmfifo_t *fifo = shmfifo_init(1234, sizeof(STU), 3);STU s;memset(&s, 0, sizeof(STU));int i;for (i = 0; i < 5; i++){shmfifo_get(fifo, &s);printf("name = %s age = %d\n", s.name, s.age);}shmfifo_destroy(fifo);return 0;
}

先运行生产者进程,输出如下:
simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v/shmfifo$ ./shmfifo_send
send ok
send ok
send ok

因为共享内存只有3块block,故发送了3次后再次P(semfull)就阻塞了,等待消费者读取数据,现在运行消费者进程
simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v/shmfifo$ ./shmfifo_recv
name = A age = 20
name = B age = 21
name = C age = 22
name = D age = 23
name = E age = 24
因为生产者已经创建了一块共享内存,故消费者只是打开而已,当读取了第一块数据之后,生产者会再次写入,依次输出后两个 send ok,可以推论的是D是重新写到共享内存开始的第一块,E是第二块,类似环形队列。
从输出可以看出,的确实现了数据的先进先出。

PS:在生产实践中也可以看到利用共享内存实现环形缓冲区 or 哈希表 的例子。

参考:《UNP》
转载自http://blog.csdn.net/jnu_simba/article/details/9103059

这篇关于linux网络编程之System V 信号量(三):基于生产者-消费者模型实现先进先出的共享内存段的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/300486

相关文章

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

linux-基础知识3

打包和压缩 zip 安装zip软件包 yum -y install zip unzip 压缩打包命令: zip -q -r -d -u 压缩包文件名 目录和文件名列表 -q:不显示命令执行过程-r:递归处理,打包各级子目录和文件-u:把文件增加/替换到压缩包中-d:从压缩包中删除指定的文件 解压:unzip 压缩包名 打包文件 把压缩包从服务器下载到本地 把压缩包上传到服务器(zip

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

Andrej Karpathy最新采访:认知核心模型10亿参数就够了,AI会打破教育不公的僵局

夕小瑶科技说 原创  作者 | 海野 AI圈子的红人,AI大神Andrej Karpathy,曾是OpenAI联合创始人之一,特斯拉AI总监。上一次的动态是官宣创办一家名为 Eureka Labs 的人工智能+教育公司 ,宣布将长期致力于AI原生教育。 近日,Andrej Karpathy接受了No Priors(投资博客)的采访,与硅谷知名投资人 Sara Guo 和 Elad G

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal