Linux操作系统7- 线程同步与互斥7(RingQueue环形队列生产者消费者模型改进)

上篇文章:Linux操作系统7- 线程同步与互斥6(POSIX信号量与环形队列生产者消费者模型)-CSDN博客

本篇代码仓库:myLerningCode/l36 · 橘子真甜/Linux操作系统与网络编程学习 - 码云 - 开源中国 (gitee.com)

目录

一. 单生产单消费单保存模型

1.1 RingQueue.hpp

1.2 Task.hpp

1.3 MainPC.cpp

1.4 测试

二. 多生产多消费模型        

2.1 分析与代码 

2.2 多生产多消费的意义


一. 单生产单消费单保存模型

        通过RingQueue可以实现生产者消费者之间的协同工作,如果现在想要将消费者的输出结果保存在文件中应该怎么办?

        可以定义两个环形队列,三个线程。让消费者充当第二个队列的生产者。

代码如下:

1.1 RingQueue.hpp

        直接使用上篇文件的代码即可。然后我们需要新增一个类,这个类中包含两个环形队列用于消费者同时访问两个队列

#pragma once
#include <iostream>
#include <vector>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
const int gnum = 10; // 阻塞队列的最大容量template <class T>
class RingQueue
{
private:void P(sem_t &sem){// P操作,申请信号量sem--int n = sem_wait(&sem);if (n < 0)std::cerr << "P操作失败" << std::endl;}void V(sem_t &sem){// V操作,释放信号量,sem++int n = sem_post(&sem);if (n < 0)std::cerr << "V操作失败" << std::endl;}public:RingQueue(const int &maxnum = gnum) : _maxnum(maxnum), _ringQueue(gnum){// 在构造函数中完成信号量的初始化int n = sem_init(&_spaceSem, 0, maxnum);if (n < 0)std::cerr << "spaceSem信号量初始化失败" << std::endl;n = sem_init(&_dataSem, 0, 0);if (n < 0)std::cerr << "dataSem信号量初始化失败" << std::endl;_producerStep = _consumerStep = 0;}~RingQueue(){sem_destroy(&_spaceSem);sem_destroy(&_dataSem);}// 生产者插入数据void push(const T &in){// 申请空间资源P(_spaceSem);// 插入数据_ringQueue[_producerStep++] = in;_producerStep %= _maxnum;// 释放一个数据资源V(_dataSem);}// 消费者获取数据void pop(T *out){// 申请数据资源P(_dataSem);// 插入数据*out = _ringQueue[_consumerStep++];_consumerStep %= _maxnum;// 释放一个空间资源V(_spaceSem);}private:std::vector<T> _ringQueue; // 使用数组来实现环形队列size_t _maxnum;sem_t _spaceSem; // 生产者空间资源信号量sem_t _dataSem;  // 消费者数据资源信号量int _producerStep; // 生产者下标int _consumerStep; // 消费者下标
};

1.2 Task.hpp

        需要新增一个保存者的任务

#pragma once
#include <iostream>
#include <cstdio>
#include <functional>class CalTask
{using func_t = std::function<int(int, int, char)>; // func是一个函数// typedef std::function<int(int,int)> func;
public:CalTask() {}CalTask(int x, int y, char op, func_t func): _x(x), _y(y), _op(op), _callback(func) {}std::string operator()(){int result = _callback(_x, _y, _op);char buffer[1024];snprintf(buffer, sizeof(buffer) - 1, "%d %c %d = %d", _x, _op, _y, result);return buffer;}// 返回任务操作的结果std::string toString(){char buffer[1024];snprintf(buffer, sizeof(buffer) - 1, "%d %c %d = ?", _x, _op, _y);return buffer;}private:int _x;int _y;char _op;         // 操作的任务的idfunc_t _callback; // 调用的函数
};class SaveTask
{typedef std::function<void(const std::string &)> func_t;public:SaveTask() {}SaveTask(const std::string &message, func_t func): _message(message), _callback(func) {}void operator()(){_callback(_message);}private:std::string _message; // 保存的信息func_t _callback;     // 将信息写入文件中
};const std::string oper = "+-*/%";
int my_math(int x, int y, char op)
{int result = 0;switch (op){case '+':result = x + y;break;case '-':result = x - y;break;case '*':result = x * y;break;case '/':{if (y == 0){std::cerr << "div zero" << std::endl;return -1;}else{result = x / y;}break;}case '%':if (y == 0){std::cerr << "moved zero" << std::endl;return -1;}else{result = x % y;}break;default:break;}return result;
}void Save(const std::string &message)
{const std::string task_pwd = "./log.txt";FILE *fp = fopen(task_pwd.c_str(), "a+");if (nullptr == fp){std::cerr << "saver open error" << std::endl;return;}fputs(message.c_str(), fp);fputc('\n', fp);fclose(fp);
}

1.3 MainPC.cpp

#include "RingQueue.hpp"
#include "Task.hpp"
#include <iostream>// 生产者
void *ProductorRoutine(void *args)
{RingQueue<CalTask> *cal_rq = (static_cast<RingQueues<CalTask, SaveTask> *>(args))->_cal_rq;while (true){int x = rand() % 20;int y = rand() % 50;const char op = oper[rand() % oper.size()];CalTask ct(x, y, op, my_math);cal_rq->push(ct);std::cout << "生产者生产任务:" << ct.toString() << " 并传递给消费者完成" << std::endl;}
}// 消费者
void *ConsumerRoutine(void *args)
{RingQueue<CalTask> *cal_rq = (static_cast<RingQueues<CalTask, SaveTask> *>(args))->_cal_rq;RingQueue<SaveTask> *save_rq = (static_cast<RingQueues<CalTask, SaveTask> *>(args))->_save_rq;while (true){CalTask ct;cal_rq->pop(&ct);std::string result = ct();std::cout << "消费者实现任务:" << result << " 实现完成!" << std::endl;SaveTask st(result, Save);save_rq->push(st);std::cout << "消费者传递任务:" << result << " 给保存者完成!" << std::endl;}
}// 保存者
void *SaverRoutine(void *args)
{RingQueue<SaveTask> *save_rq = (static_cast<RingQueues<CalTask, SaveTask> *>(args))->_save_rq;while (true){SaveTask st;save_rq->pop(&st);st();std::cout << "保存者保存任务完成!" << std::endl;}
}void test1()
{RingQueues<CalTask, SaveTask> *rqs = new RingQueues<CalTask, SaveTask>;rqs->_cal_rq = new RingQueue<CalTask>();rqs->_save_rq = new RingQueue<SaveTask>();pthread_t p, c, s;pthread_create(&p, nullptr, ProductorRoutine, rqs);pthread_create(&c, nullptr, ConsumerRoutine, rqs);pthread_create(&s, nullptr, SaverRoutine, rqs);pthread_join(p, nullptr);pthread_join(c, nullptr);pthread_join(s, nullptr);delete rqs;
}int main()
{srand((unsigned int)time(0) ^ getpid() ^ pthread_self());test1();return 0;
}

1.4 测试

        运行结果如下:

二. 多生产多消费模型        

2.1 分析与代码 

        RingQueue环形队列可以保证单个生产者和单个消费者之间的同步与互斥,如果现在有多个生产者和多个消费者的话。如何保证生产者之间的互斥?消费者者之间的互斥?

        阻塞队列中,我们通过加锁的方式让同一时刻只能有一个生产者线程进入临界区或者一个消费者进入临界区。

        而环形队列中, 通过信号量保证了生产者消费者之间的同步与互斥。如果想要保证消费者与消费者之间的互斥,生产者与生产者之间的互斥,也需要加锁保护

        在RingQueue中添加两个成员变量,一个生产者互斥锁,一个消费者互斥锁。同时需要在构造函数中完成锁的初始化,析构函数中完成锁的销毁。

        并且在push函数中加生产者锁,在pop函数中加消费者锁。以实现生产者与生产者之间的互斥和消费者与消费者之间的互斥。(本质是防止多个线程同时访问导致生产者下标或者消费者下标出现数据错误) 

代码如下:

#pragma once
#include <iostream>
#include <vector>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
const int gnum = 10; // 阻塞队列的最大容量template <class T>
class RingQueue
{
private:void P(sem_t &sem){// P操作,申请信号量sem--int n = sem_wait(&sem);if (n < 0)std::cerr << "P操作失败" << std::endl;}void V(sem_t &sem){// V操作,释放信号量,sem++int n = sem_post(&sem);if (n < 0)std::cerr << "V操作失败" << std::endl;}public:RingQueue(const int &maxnum = gnum) : _maxnum(maxnum), _ringQueue(gnum){// 在构造函数中完成信号量的初始化int n = sem_init(&_spaceSem, 0, maxnum);if (n < 0)std::cerr << "spaceSem信号量初始化失败" << std::endl;n = sem_init(&_dataSem, 0, 0);if (n < 0)std::cerr << "dataSem信号量初始化失败" << std::endl;_producerStep = _consumerStep = 0;// 初始化锁pthread_mutex_init(&_pmtx, nullptr);pthread_mutex_init(&_cmtx, nullptr);}~RingQueue(){// 销毁信号量与互斥锁sem_destroy(&_spaceSem);sem_destroy(&_dataSem);//pthread_mutex_destroy(&_cmtx);pthread_mutex_destroy(&_pmtx);}// 生产者插入数据void push(const T &in){// 生产者加锁,保证生产者与生产者之间的互斥pthread_mutex_lock(&_pmtx);// 申请空间资源P(_spaceSem);// 插入数据_ringQueue[_producerStep++] = in;_producerStep %= _maxnum;// 释放一个数据资源V(_dataSem);// 解锁pthread_mutex_unlock(&_pmtx);}// 消费者获取数据void pop(T *out){// 消费者加锁pthread_mutex_lock(&_cmtx);// 申请数据资源P(_dataSem);// 插入数据*out = _ringQueue[_consumerStep++];_consumerStep %= _maxnum;// 释放一个空间资源V(_spaceSem);// 解锁pthread_mutex_unlock(&_cmtx);}private:std::vector<T> _ringQueue; // 使用数组来实现环形队列size_t _maxnum;sem_t _spaceSem; // 生产者空间资源信号量sem_t _dataSem;  // 消费者数据资源信号量int _producerStep; // 生产者下标int _consumerStep; // 消费者下标pthread_mutex_t _pmtx;pthread_mutex_t _cmtx;
};

MainPC.cpp

#include <iostream>
#include <memory>
#include <string>#include <unistd.h>
#include <pthread.h>
#include "RingQueue.hpp"
#include "Task.hpp"const std::string OP = "+-*/%";
void *producer(void *args)
{// 获取交易场所 - 阻塞队列RingQueue<CalTask> *rq = static_cast<RingQueue<CalTask> *>(args);while (true){int x = rand() % 100;int y = rand() % 100;char op = OP[rand() % OP.size()];// 打印日志printf("生产者生产的数据:%d %c %d 并交给消费者计算\n", x, op, y);CalTask ct(x, y, op, my_math);rq->push(ct);}return nullptr;
}void *consumer(void *args)
{// 获取交易场所 - 生产消费阻塞队列,消费保存阻塞队列RingQueue<CalTask> *rq = static_cast<RingQueue<CalTask> *>(args);while (true){// 获取任务计算CalTask ct;rq->pop(&ct);std::string result = ct();std::cout << "消费者获取数据并计算:" << result << std::endl;}return nullptr;
}int main()
{srand((unsigned int)time(0) ^ getpid());// 建立任务队列和保存队列RingQueue<CalTask> *rq = new RingQueue<CalTask>;pthread_t c[3], p[3];pthread_create(&c[0], nullptr, consumer, (void *)rq);pthread_create(&c[1], nullptr, consumer, (void *)rq);pthread_create(&c[2], nullptr, consumer, (void *)rq);pthread_create(&p[0], nullptr, producer, (void *)rq);pthread_create(&p[1], nullptr, producer, (void *)rq);pthread_create(&p[2], nullptr, producer, (void *)rq);pthread_join(c[0], nullptr);pthread_join(c[1], nullptr);pthread_join(c[2], nullptr);pthread_join(p[0], nullptr);pthread_join(p[1], nullptr);pthread_join(p[2], nullptr);delete rq;return 0;
}

测试结果如下:

可以看到,长时间没有出错

如果将加锁解锁操作进行注释:

会出现段错误,因为访问了非法内存

2.2 多生产多消费的意义

        与阻塞队列BlockQueue一样,多生产多消费的时候。生产线程生产数据可能需要很多时间,一个生产者生产者访问环形队列的时候不妨碍其他线程生产自己的资源。一个消费者访问环形队列拿数据的时候不妨碍其他消费者拿到数据进行处理

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

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

相关文章

全面讲解python的uiautomation包

在常规的模拟鼠标和键盘操作&#xff0c;我们一般使用pyautogui&#xff0c;uiautomation模块不仅能直接支持这些操作&#xff0c;还能通过控件定位方式直接定位到目标控件的位置&#xff0c;而不需要自己去获取对应坐标位置。uiautomation模块不仅支持任意坐标位置截图&#x…

图解CNN、RNN、LSTM

一、CNN 二、RNN 三、LSTM 以上笔记参考自b站up主 自然卷小蛮&#xff08;自然卷小蛮的个人空间-自然卷小蛮个人主页-哔哩哔哩视频&#xff09;&#xff0c;感兴趣的可以去深入了解。

3.25学习总结 抽象类和抽象方法+接口+内部类+API

抽象类和抽象方法&#xff1a; 有抽象方法&#xff0c;那么类肯定是抽象类。父类不一定是抽象的&#xff0c;但如果父类中有抽象方法那一定是抽象类。 如果子类中都存在吃这个行为&#xff0c;但吃的具体东西不同&#xff0c;那么吃这个行为定义在父类里面就是抽象方法&#x…

Ubuntu22.04 UEFI系统配置Apache Tomcat/8.5.87为开机自动启动

前置条件&#xff0c;Java与Tomcat目录均为/usr/local路径下。 java安装目录为&#xff1a;/usr/local/java tomcat安装目录为&#xff1a;/usr/local/tomcat 1. 创建 Tomcat 专用用户和组&#xff08;可选但推荐&#xff09; # 创建 tomcat 用户组 sudo groupadd tomcat#…

MySQL复习

1基本操作复习 1.1数据库创建 创建数据库create database 数据库名;判断再创建数据库create database if not exists 数据库名;创建数据库指定字符集create database 数据库名 character set 字符集;创建数据库指定排序方式create database 数据库名 collate 排序方式;创建数据…

数据结构—树(java实现)

目录 一、树的基本概念1.树的术语2.常见的树结构 二、节点的定义三、有关树结构的操作1.按照数组构造平衡 二叉搜索树2.层序遍历树3.前、中、后序遍历树(1).前序遍历树(2).中序遍历树(3).后序遍历树(4).各种遍历的情况的效果对比 4.元素添加5.元素删除1.删除叶子节点2.删除单一…

SPI 机制与 Spring Boot AutoConfiguration 对比解析

一、架构效率革命性提升 1.1 类加载效率跃升 Spring Boot 2.7引入的AutoConfiguration.imports采用清单式配置加载&#xff0c;对比传统SPI机制&#xff1a; 传统SPI扫描路径&#xff1a;META-INF/services/** Spring Boot新方案&#xff1a;META-INF/spring/org.springfram…

node-red dashboard

安装&#xff1a; npm install node-red-dashboard 访问&#xff1a; http://127.0.0.1:1880/ui 1. 创建一个新的 Dashboard 页面: 在 Node-RED 编辑器中&#xff0c;拖动一个 ui_dashboard 节点到工作区&#xff0c;并将其连接到你的数据流。 2. 配置 Dashboard 节点: 双击…

深入理解现代C++在IT行业中的核心地位与应用实践

深入理解现代C在IT行业中的核心地位与应用实践 一、C在IT行业中的不可替代性 现代IT行业中&#xff0c;C凭借其零成本抽象和系统级控制能力&#xff0c;在以下关键领域保持不可替代地位&#xff1a; 应用领域C优势体现典型应用案例高性能计算直接内存管理&#xff0c;SIMD指令…

医院挂号预约小程序|基于微信小程序的医院挂号预约系统设计与实现(源码+数据库+文档)

医院挂号预约小程序 目录 基于微信小程序的医院挂号预约系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、小程序用户端 2、系统服务端 &#xff08;1&#xff09; 用户管理 &#xff08;2&#xff09;医院管理 &#xff08;3&#xff09;医生管理 &#xf…

2025最新版Ubuntu Server版本Ubuntu 24.04.2 LTS下载与安装-详细教程,细致到每一步都有说明

官网 https://ubuntu.com/ 下载 点击菜单 Prodercts> Ubuntu OS>Ubuntu Server 点击下载 下载后会有个弹窗 安装 选择第一个 install Ubuntu Server 直接默认&#xff0c;选择English 【默认】 选择键盘布局【默认】 选择安装配置【默认】 配置网络 我这里选择…

【AI】NLP

不定期更新&#xff0c;建议关注收藏点赞。 目录 transformer大语言模型Google Gemma疫情网民情绪识别 整体框架 baseline构建 模型调参、模型优化、其他模型 数据trick、指标优化、magic feature 数据增强、伪标签、迁移学习 模型融合sklearn中TFIDF参数详解 频率阈值可以去掉…

vscode正则表达式使用

小标题 ^\d.\d.\d\s.*$ ^表示匹配字符串的开头。\d\.\d\.\d表示匹配一到多个数字&#xff0c;接着一个小数点&#xff0c;再接着一到多个数字&#xff0c;然后又一个小数点和一到多个数字&#xff0c;用来匹配类似 “2.1.1” 这样的标题号部分。\s表示匹配一个空格。.*表示匹配…

TCP/IP三次握手的过程,为什么要3次?

一&#xff1a;过程 第一次&#xff08;SYN&#xff09;&#xff1a; 客户端发送一个带有SYN标志的TCP报文段给服务器&#xff0c;设置SYN1&#xff0c;并携带初始序列号Seqx&#xff08;随机值&#xff09;&#xff0c;进入SYN_SENT状态。等待服务器相应。 第二次&#xff08…

vue-将组件内容导出为Word文档-docx

1. 安装依赖 首先&#xff0c;我们需要安装docx库&#xff0c;以便在前端生成Word文档。可以通过以下命令进行安装&#xff1a; npm install docx 2. 实现导出功能 2.1 初始化文档 使用docx库创建一个新的文档实例&#xff0c;并定义文档的结构和内容。我们使用Document、…

uni-app常用模板

列表样式一 ,下拉翻页查询,效果图及代码 <template><z-paging ref="paging" v-model="dataList" @query="queryList"><!-- 需要固定在顶部不滚动的view放在slot="top"的view中,如果需要跟着滚动,则不要设置slot=&q…

鸿蒙移动应用开发--UI组件布局

实验要求&#xff1a; 制作一个B站视频卡片界面&#xff0c;大致如下图所示&#xff0c;要求应用到线性布局、层叠布局等相关课堂知识。背景图、logo及文本内容不限。 实验环境 &#xff1a;DevEco Studio 实验过程&#xff1a; 步骤1&#xff1a;创建项目 1. 在您的开发环境…

NVIDIA TensorRT 深度学习推理加速引擎详解

NVIDIA TensorRT 深度学习推理加速引擎详解 文章目录 NVIDIA TensorRT 深度学习推理加速引擎详解引言文章结构 第一部分&#xff1a;TensorRT概述什么是TensorRT&#xff1f;TensorRT的核心功能和优势1. 图优化2. 量化支持3. 动态形状支持4. 多平台支持5. 编程接口6. 性能优势 …

如何用Spring AI构建MCP Client-Server架构

现代 Web 应用正加速与大语言模型(LLMs)深度融合,构建超越传统问答场景的智能解决方案。为突破模型知识边界,增强上下文理解能力,开发者普遍采用多源数据集成策略,将 LLM 与搜索引擎、数据库、文件系统等外部资源互联。然而,异构数据源的协议差异与格式壁垒,往往导致集…

gradio调用多个CSS的HTML页

很多博客介绍的gradio读取html和css比较简单&#xff0c;如果要做很细致的前端页面优化&#xff0c;比如丰富的响应式的cssjs&#xff0c;至少要有html多个css&#xff0c;是暂不能实现的。bootstrap、font-awesome、jquery等 方案一当然是直接更换htmlcss为主的部署方式&#…