【Linux】生产消费模型实践 --- 基于信号量的环形队列

基于信号量的环形队列
- 1 信号量
- 2 框架构建
- 3 代码实现
- 4 测试运行
1 信号量
信号量本质是一个计数器,可以在初始化时对设置资源数量,进程 / 线程 可以获取信号量来对资源进行操作和结束操作可以释放信号量!
用于多进程 / 多线程 对共享数据对象的读取,它和管道有所不同,它不以传送数据为主要目的,它主要是用来保护共享资源(信号量也属于临界资源),使得资源在一个时刻只有一个进程独享。 在资源只有一个时就一把互斥锁!
信号量只能进行两种操作获取等待和释放信号,即PV操作:
- P(sv):我们将申请获取信号量称为P操作,申请信号量的本质就是申请获得临界资源中某块资源的使用权限,当申请成功时临界资源中资源的数目应该减去一。所以P操作的本质就是让计数器减一,如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行。对应的接口为,使用很简单:
#include <semaphore.h> //阻塞等待获取 int sem_wait(sem_t *sem); //只进行一次获取,非阻塞等待 int sem_trywait(sem_t *sem); //时间片内进行等待,超出就退出阻塞! int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout); - V(sv):我们将释放信号量称为V操作,释放信号量的本质就是归还临界资源中某块资源的使用权限,当释放成功时临界资源中资源的数目就应该加一。所以V操作本质就是让计数器加一,如果有其他进程 / 线程因等待sv而被挂起,就发送信号让它恢复运行,如果没有进程 / 线程因等待信号量而挂起,就给他加1。对应接口为:
#include <semaphore.h> //释放获取的信号量 int sem_post(sem_t *sem);
PV操作都是原子的,不用担心线程安全!此外信号量初始化和销毁的接口是:
- 信号量初始化:
参数分别为:#include <semaphore.h> int sem_init(sem_t *sem, int pshared, unsigned int value);- sem_t *sem:传入信号量的地址
- pshared:传入0值表示线程间共享,传入非零值表示进程间共享。
- value:信号量的初始值(计数器的初始值)。
- 信号量销毁:
#include <semaphore.h> int sem_destroy(sem_t *sem);
2 框架构建
-
环形队列的成员变量
- 线性容器vector模拟环形队列
- 最大容量
int _max_step - 消费者位置
_c_step与 生产者位置_p_step - 两个信号量来表示生产与消费的剩余容量
sem_t _data_sem: 当前有多少数据
sem_t _space_sem: 当前剩余空间还有多少
-
构造函数初始化
- 最大容量需要给值初始化
- 两个初始位置都为 0
- 信号量初始化 sem_init() 数据为 0 ,空间为 最大容量
-
Push接口用来加入数据
- 首先需要申请信号量 P 来对空间信号量进行获取 sem_wait (&sem_t _space_sem)(申请信号量是原子的)
获取信号量的本质是对资源 – - 生产进行插入 , 对应下标向后移动 , 注意不能越界
- 最后进行释放信号量 V 来对资源信号量进行释放 sem_post()
释放信号量的本质是对资源 ++
- 首先需要申请信号量 P 来对空间信号量进行获取 sem_wait (&sem_t _space_sem)(申请信号量是原子的)
-
Pop接口用来获取数据
- 首先需要申请信号量 P 来对资源信号量进行获取 sem_wait (&sem_t _space_sem)(申请信号量是原子的)
获取信号量的本质是对资源 – - 获取队列资源,并进行释放, 对应下标向后移动 , 注意不能越界
- 最后进行释放信号量 V 来对空间信号量进行释放 sem_post()
释放信号量的本质是对资源 ++
- 首先需要申请信号量 P 来对资源信号量进行获取 sem_wait (&sem_t _space_sem)(申请信号量是原子的)
-
多生产多消费改造:多个生产 / 消费线程存在 消费对消费 生产对生产的问题!
- 信号量保证了单生产单消费中,两个线程可以通过信号量来保证不会出现访问越界 / 访问重叠的问题!
- 多线程的情况下可能会发生访问同一位置的可能,获取到信号量之后由于中间的处理是临界区,可能会发生线程的切换,就会导致对同一位置进行处理,进而发生问题!
- 为了保证线程安全,需要两把锁,分别管理生产者和消费者!
- 锁的处理:
- 获取信号量之后再进行加锁,获取信号量是原子的,先申请信号量可以保证多个线程在获取中进行排队等待。
- 如果先加锁,就只能使一个线程进入到获取信号量的队列中,效率低(电影院先买票在排队 ,先排队再买票)
6.为什么信号量不加条件判断?:
在环形队列的实现中,没有使用条件变量,像阻塞队列一样进行条件的判断 而是直接来不管三七二十一进行获取信号量,因为信号量本身就是判断条件,信号量是用来描述内部资源的多少的,是原子的!本质是一个计数器 通过预订机制来保证内部资源的合理使用,当信号量的资源数量为1时和锁时等价的!
3 代码实现
#pragma once#include <vector>
#include <semaphore.h>const int default_cap = 5;template <class T>
class RingQueue
{
public:RingQueue(int max_cap = default_cap) : _rq(max_cap), _max_cap(max_cap), _p_step(0), _c_step(0){// 信号量初始化sem_init(&_space_sem, 0, _max_cap);sem_init(&_data_sem, 0, 0);//锁进行初始化pthread_mutex_init(&_c_mtx , nullptr);pthread_mutex_init(&_p_mtx , nullptr);}// 获取信号量void P(sem_t &sp){sem_wait(&sp);}// 释放信号量void V(sem_t &sp){sem_post(&sp);}// 插入操作void Push(const T &t){// 获取空间信号量 --P(_space_sem);//临界区上锁pthread_mutex_lock(&_p_mtx );_rq[_p_step] = t;_p_step++;_p_step %= _max_cap;//解锁pthread_mutex_unlock(&_p_mtx);// 释放信号量 ++V(_data_sem);}// 获取操作void Pop(T *t){// 获取资源信号量P(_data_sem);pthread_mutex_lock(&_c_mtx);*t = _rq[_c_step];_c_step++;_c_step %= _max_cap;pthread_mutex_unlock(&_c_mtx);// 释放信号量V(_space_sem);}~RingQueue(){// 销毁对应信号量!sem_destroy(&_space_sem);sem_destroy(&_data_sem);//锁进行释放pthread_mutex_destroy(&_c_mtx);pthread_mutex_destroy(&_p_mtx);}private:// 底层线性结构,模拟环形队列std::vector<T> _rq;// 最大容量int _max_cap;// 生产者/消费者 下标int _p_step;int _c_step;// 空间/资源 信号量sem_t _space_sem;sem_t _data_sem;// 生产 / 消费 锁pthread_mutex_t _p_mtx;pthread_mutex_t _c_mtx;};
4 测试运行
我们来做一些简单测试,我们设计了Task类,用于执行加法操作。它包含两个整型参数_x和_y,并提供方法来执行加法并获取结果。通过重载括号运算符,Task对象可以被直接调用以执行计算。此外,类还提供了调试信息和结果输出的功能。
我写了一段代码段用于测试。在该测试中:定义了两个线程函数Consumer和Productor,分别模拟消费者和生产者行为:
Consumer线程不断从环形队列中取出Task对象,执行其操作,并打印消费结果。Productor线程则持续生成新的Task对象并将其放入队列中,同时打印出生产信息。
主函数main中创建了一个容量为5的RingQueue<Task>实例,并启动了两个线程。pthread_create用于创建线程,pthread_join确保主线程等待子线程执行完毕。通过这种方式,我们验证了环形队列在多线程环境下的线程安全性和功能正确性。
#include <iostream>
#include "RingQueue.hpp"
#include <pthread.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include "Task.hpp"void *Consumer(void *args)
{RingQueue<Task> *rq = static_cast<RingQueue<Task> *>(args);srand(time(nullptr) ^ getpid());while (true){// 不断的进行获取Task data ;rq->Pop(&data);data();std::cout << "Consumer 消费者消费 -> " << data.result() << std::endl;sleep(1);}
}
void *Productor(void *args)
{RingQueue<Task> *rq = static_cast<RingQueue<Task> *>(args);srand(time(nullptr) ^ getpid());while (true){// 不断的进行写入int num1 = rand() % 10;usleep(1000);int num2 = rand() % 10;Task t(num1 , num2);rq->Push(t);std::cout << "Productor 生产者生产 -> " << t.debug() << std::endl;usleep(10000);}
}int main()
{// 环形队列RingQueue<Task> rq(5);// 使用两个线程来测试pthread_t t1, t2;pthread_create(&t1, nullptr, Consumer, &rq);pthread_create(&t2, nullptr, Productor, &rq);pthread_join(t1, nullptr);pthread_join(t2, nullptr);
}
运行效果:

很好的完成了任务!!!
相关文章:
【Linux】生产消费模型实践 --- 基于信号量的环形队列
你送出去的每颗糖都去了该去的地方, 其实地球是圆的, 你做的好事终会回到你身上。 --- 何炅 --- 基于信号量的环形队列 1 信号量2 框架构建3 代码实现4 测试运行 1 信号量 信号量本质是一个计数器,可以在初始化时对设置资源数量…...
Science Robotics 与蜜蜂群互动的蜂窝型机器人系统
蜜蜂,如黄蜂,蚂蚁和其他社会昆虫,建立大型自组织群体,通常被解释为自我调节的“超有机体”。这些超生物是生态系统的重要稳定剂,因此被认为是“关键物种”。例如,蜜蜂群落通过觅食授粉服务的生态效应对陆地…...
Vue 计算属性:优雅地处理数据逻辑
在 Vue.js 中,计算属性(Computed Properties)是一种非常实用的功能,它允许我们根据组件的响应式依赖进行缓存和派生状态。计算属性可以让我们以声明式的方式编写复杂的逻辑,而不必担心性能问题。 什么是计算属性&…...
C++中`union`
文章目录 C中的union什么是union?定义union示例一输出结果: 示例二修正后的代码解释输出结果结论 union的特性匿名union示例 union和struct的区别1. 内存布局2. 同时访问3. 用途 union和class的区别1. 数据成员2. 功能性3. 适用场景 在C编程中࿰…...
Linux——网络(1)
一、IPC(进程间通信方式) IPC:Inter Process Communication 共享内存(最高效的进程间通信方式) 虚拟地址 mmu(memory management unit ) 共享内存: 1.是一块,内核预留的空间 2.最高效的…...
【五】阿伟开始学Kafka
阿伟开始学Kafka 概述 人生若只如初见,阿伟心里回想起了第一次和Kafka见面的场景,记忆虽然已经有些模糊,但是感觉初次见面是美好的。积累了一些实战经验之后,阿伟感觉不能再是面对百度开发了,于是决心系统的学习一下Ka…...
Java—Arrays api
public static String toString(数组) //把数组拼接成一个字符串 public static int binarySearch(数组,查找的元素) //二分查找法查找元素 public static int[] copyOf(原数组,新数组长度) //拷贝数组 public st…...
Java - 基数排序算法介绍、应用场景和示例代码
概述 基数排序(Radix Sort)是一种非比较型整数排序算法,适用于整数或固定长度的字符串排序。它的基本思想是将待排序的元素分为多个关键字进行排序,通常从最低位(最低有效位,Least Significant Digit, LSD…...
Django 后端架构开发:文件云存储,从本地存储到腾讯COS桶集成
⭐ Django 后端架构开发:文件云存储,从本地存储到腾讯COS桶集成 目录 ☁️ 文件云存储 - 项目使用云存储💻 文件云存储 - 项目中使用本地存储📝 文件云存储 - 概述和创建项目🌐 腾讯COS桶 - 概述📚 腾讯CO…...
【系统分析师】-综合知识-计算机网络与信息安全
1、要对消息明文进行加密传送,当前通常使用的加密算法是 报文认证算法:数字摘要 RSA 非对称加密,一般不用于明文 MD5 数字摘要 SHA-1 数字摘要,160位的消息摘要 HMAC 以一个密钥和一个消息为输入,生成一个消息摘要作…...
C++ | Leetcode C++题解之第363题矩形区域不超过K的最大数值和
题目: 题解: class Solution { public:int maxSumSubmatrix(vector<vector<int>> &matrix, int k) {int ans INT_MIN;int m matrix.size(), n matrix[0].size();for (int i 0; i < m; i) { // 枚举上边界vector<int> sum(…...
python动画:场景的线性变换展示
一,主函数 LinearTransformationScene 是 Manim 中用于展示线性变换的场景类。它通过在一幅背景和前景平面上展示向量和变换,帮助理解线性代数中的概念。 LinearTransformationScene(include_background_planeTrue, include_foreground_planeTrue, ba…...
HBase体系架构与环境搭建
这里写目录标题 一、常见的NoSQL数据库二、HBase的体系架构和表结构三、搭建HBasa环境1.本地模式2.伪分布模式全分布模式HA模式 一、常见的NoSQL数据库 NoSQL数据库的说明与定义 NoSQL是一种不同于关系数据库的数据库管理系统设计方式,是对非关系型数据库的统称。它…...
海思SD3403/SS928V100开发(16)Tsensor驱动开发
1. 前言 由于需要检测SD3403芯片内部实时温度,需要开发Tsensor传感器驱动和应用 查看手册发现SD3403内部有三个Tsensor传感器 可以参考之前我写的35系列平台Tsensor驱动开发记录 海思35系列平台Tsensor驱动开发(1)驱动编写_t sensor-CSDN博客 海思35系列平台Tsensor驱动…...
JVM类加载机制—JVM类加载过程
一、概述 代码编译后,就会生成JVM(Java虚拟机)能够识别的二进制字节流文件(*.class)。而JVM把Class文件中的类描述数据从文件加载到内存,并对数据进行校验、转换解析、初始化,使这些数据最终成…...
可变参数模板与包装器
抱歉:铁汁们,最近在做兼职,积累社会经验,多有拖欠,请多多包涵(抱拳) 引子:接上回我们讲了C11的几种新增,今天就来接着讲C11中比较有用的二个东西可变参数模板与包装器。…...
工业控制常用“对象“数据类型汇总(数据结构篇)
合理巧妙的数据结构会大大简化项目的编程工作量,所以任何项目前期第一步应该是设计巧妙的数据结构、封装对象属性。这样会使我们的编程快捷和高效。这篇博客作为数据类型汇总,会不间断更新。 1、普通电机轴对象 2、普通电机轴对象(详细结构变量) TYPE "udtMotorAxis&q…...
优雅处理枚举前端丢失大Long精度问题
1. 枚举-json处理(前端 <> 后端 <> 数据库) 前端传递 枚举code 后端响应 枚举code 表里存储 枚举code 内存处理 枚举对象 Getter AllArgsConstructor JsonFormat(shape JsonFormat.Shape.OBJECT) public enum SexEnum {MALE(0, "男&…...
【c/c++】 学习ector 容器笔记
c/c 学习ector 容器笔记 int 型的 vector 容器应该使用什么类型的索引? 对于 int 型的 vector 容器,应该使用 size_t 类型的索引。size_t 是一个无符号整数类型,它在标准库中广泛用于表示大小和索引。它足够大,可以表示任何标准…...
DN专业3D图形制作软件win/mac软件安装下载(附下载链接)
目录 一、软件概述 1.1 Adobe DN简介 1.2 Windows/Mac系统要求 Windows系统: Mac系统: 二、安装步骤 2.1 下载与解压 2.2 安装程序 2.3 启动软件 三、使用教程 3.1 界面介绍 3.2 创建和编辑3D内容 3.3 合成与渲染 四、高级技巧与注意事项 …...
iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘
美国西海岸的夏天,再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至,这不仅是开发者的盛宴,更是全球数亿苹果用户翘首以盼的科技春晚。今年,苹果依旧为我们带来了全家桶式的系统更新,包括 iOS 26、iPadOS 26…...
STM32F4基本定时器使用和原理详解
STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...
JVM虚拟机:内存结构、垃圾回收、性能优化
1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...
Mysql中select查询语句的执行过程
目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析(Parser) 2.4、执行sql 1. 预处理(Preprocessor) 2. 查询优化器(Optimizer) 3. 执行器…...
Golang——9、反射和文件操作
反射和文件操作 1、反射1.1、reflect.TypeOf()获取任意值的类型对象1.2、reflect.ValueOf()1.3、结构体反射 2、文件操作2.1、os.Open()打开文件2.2、方式一:使用Read()读取文件2.3、方式二:bufio读取文件2.4、方式三:os.ReadFile读取2.5、写…...
基于PHP的连锁酒店管理系统
有需要请加文章底部Q哦 可远程调试 基于PHP的连锁酒店管理系统 一 介绍 连锁酒店管理系统基于原生PHP开发,数据库mysql,前端bootstrap。系统角色分为用户和管理员。 技术栈 phpmysqlbootstrapphpstudyvscode 二 功能 用户 1 注册/登录/注销 2 个人中…...
通过MicroSip配置自己的freeswitch服务器进行调试记录
之前用docker安装的freeswitch的,启动是正常的, 但用下面的Microsip连接不上 主要原因有可能一下几个 1、通过下面命令可以看 [rootlocalhost default]# docker exec -it freeswitch fs_cli -x "sofia status profile internal"Name …...
恶补电源:1.电桥
一、元器件的选择 搜索并选择电桥,再multisim中选择FWB,就有各种型号的电桥: 电桥是用来干嘛的呢? 它是一个由四个二极管搭成的“桥梁”形状的电路,用来把交流电(AC)变成直流电(DC)。…...
32单片机——基本定时器
STM32F103有众多的定时器,其中包括2个基本定时器(TIM6和TIM7)、4个通用定时器(TIM2~TIM5)、2个高级控制定时器(TIM1和TIM8),这些定时器彼此完全独立,不共享任何资源 1、定…...
STM32标准库-ADC数模转换器
文章目录 一、ADC1.1简介1. 2逐次逼近型ADC1.3ADC框图1.4ADC基本结构1.4.1 信号 “上车点”:输入模块(GPIO、温度、V_REFINT)1.4.2 信号 “调度站”:多路开关1.4.3 信号 “加工厂”:ADC 转换器(规则组 注入…...
