Linux -- 生产消费模型之环形队列、信号量
目录
前言
环形队列
如何用环形队列实现生产消费模型?
信号量
sem_t
sem_init(初始化信号量)
sem_destroy(销毁信号量)
什么是PV操作?
sem_wait(P操作,减少信号量)
sem_post(V操作,增加信号量)
用信号量实现环形队列
代码
Thread.hpp 代码:
Task.hpp 代码:
RingQueue.hpp 代码:
test.cc 代码:
结果:
前言
Linux -- 生产消费模型-CSDN博客
https://blog.csdn.net/2301_76973016/article/details/144733376?spm=1001.2014.3001.5501在之前实现生产消费模型的代码中,我们把整个阻塞队列(缓冲区)视为一个整体进行访问,整个缓冲区只能由生产者或者消费者访问,不能生产者和消费者同时访问,不能边读取数据边放入数据,为了达到这一目标,我们可以用环形队列来实现。
环形队列
环形队列是一种线性数据结构,其操作基于先进先出(FIFO, First In First Out)原则,但它的最后一个元素连接到第一个元素以形成一个环。这种结构可以更有效地利用存储空间,因为它允许在队列的末尾达到数组的末端时“环绕”回到数组的开始。
如何用环形队列实现生产消费模型?
我们用环形队列来实现缓冲区!
当缓冲区中没有数据,即环形队列为空,即消费者和生产者指向同一个位置时时,消费者没办法消费,所以只能生产者先走,让生产者先去输出数据,此时消费者休眠。生产者每生产一个数据,数据就会占一个空间,所以生产者往前走了一步,_productor_step++,继续生产,当环形队列中有数据时,就可以唤醒消费者:
当缓冲区中的数据满了,即环形队列为满,如果生产者继续往前走,即回到了环形队列的头,生产者继续生产则会覆盖之前的数据,那么这时只能让消费者先走,让消费者取出数据,腾出空间,此时生产者休眠,当有空间时,就可以唤醒生产者:
当环形队列既不为空,也不为满时,此时生产者和消费者谁先走都没关系,两者互不干扰,也就是生产者和消费者可以实现并发地访问缓冲区!直到环形队列再次为空或者为满时,再来调整让消费者先走还是生产者先走。
![]()
从上面的三种情况可以看出:
- 生产者关注的是环形队列的空间资源,而消费者关注的是环形队列中的数据资源,;
- 生产者每生产一个数据,空间资源 -1,数据资源 +1;
- 消费者每取出一个数据,空间资源 +1,数据资源 -1;
- 两者都申请了自己需要的资源,释放了对方需要的资源。
为了实现这一过程,我们采用信号量来实现。
信号量
信号量主要用于多线程或多进程编程中的同步机制,以确保多个线程或进程能够安全地共享资源或协调它们的行为。通过使用信号量,可以控制同时访问特定资源的线程或进程数量,从而避免竞争条件和其他并发问题。
信号量其实是一个计数器,是对资源的预定机制:
- 如果信号量申请成功,相当于资源申请成功了,我们就可以使用这个资源;
- 如果信号量申请失败,就会在信号量下的队列阻塞等待。
sem_t
sem_t是 POSIX 标准定义的一种用于实现进程间同步的互斥锁和信号量的数据类型。
sem_init(初始化信号量)
#include <semaphore.h>int sem_init(sem_t *sem, int pshared, unsigned int value);
sem_t *sem: 指向要初始化的信号量对象的指针。
int pshared: 决定信号量是否可以在多个进程间共享。
- 如果设置为
0,则信号量仅限于同一进程内的线程之间共享。- 如果设置为非零值,则信号量可以在不同进程之间共享(具体支持取决于操作系统实现)。
unsigned int value: 信号量的初始值。这个值代表了可以同时进行的并发操作的数量。
sem_destroy(销毁信号量)
#include <semaphore.h>int sem_destroy(sem_t *sem);
sem_t *sem: 指向要销毁的信号量对象的指针。
什么是PV操作?
PV操作是操作系统中用于进程同步的一种机制,通常与信号量相关联。PV操作的名字来源于荷兰语“Probeer te Verlagen”(尝试减少)和“Verhoog”(增加),在中文文献中也常被称为“P操作”和“V操作”。这两个操作一起构成了信号量的基本操作,用来控制对共享资源的访问,以确保多个进程或线程之间能够正确地协调工作。
- P操作(Proberen te Verlagen, 尝试减少)是指尝试将信号量的值减一;
- V操作(Verhoog, 增加)是指将信号量的值加一。
PV操作主要用于解决临界区问题、互斥访问以及进程间的同步。它们确保了多个进程或线程不会同时访问相同的共享资源,从而避免了数据竞争和不一致的状态。
sem_wait(P操作,减少信号量)
#include <semaphore.h>int sem_wait(sem_t *sem);
sem_t *sem: 指向要操作的信号量对象的指针。
如果信号量的当前值为零,那么
sem_wait调用将会阻塞,直到信号量的值变为正数,然后将信号量的值减一。这通常用来控制对共享资源的访问,确保同一时间只有一个或指定数量的线程可以访问该资源。
sem_post(V操作,增加信号量)
#include <semaphore.h>int sem_post(sem_t *sem);
sem_t *sem: 指向要操作的信号量对象的指针。
它会增加信号量的值,并唤醒一个或多个等待该信号量的线程。如果当前没有线程在等待这个信号量,那么这次增加操作只是简单地增加了信号量的值。
用信号量实现环形队列
有了信号量之后,就可以把 环形队列 的使用 用信号量来表示,下面用伪代码简单了解一下,其中取模是为了当走到环形队列的尾时,再走一步可以回到环形队列的头:
- 对于生产者:
P(room);//申请空间资源//信号量申请成功,可以使用该空间,且空间资源-1 ring_queue[_productor_step] = in;//生产 _productor_step++; _productor_step %= n;V(data);//数据资源+1
- 对于消费者:
P(data);//申请数据资源//信号量申请成功,可以使用该数据,且数据资源-1 out = ring_queue[_consumer_step];//消费 _consumer_step++; _consumer_step %= n;V(room);//空间资源+1
代码
Thread.hpp 代码:
#ifndef __THREAD_HPP__
#define __THREAD_HPP__#include <iostream>
#include <string>
#include <unistd.h>
#include <functional>
#include <pthread.h>namespace ThreadModule
{template <typename T>using func_t = std::function<void(T &, std::string name)>;// typedef std::function<void(const T&)> func_t;template <typename T>class Thread{public:void Excute(){_func(_data, _threadname);}public:Thread(func_t<T> func, T &data, const std::string name = "none-name"): _func(func), _data(data), _threadname(name), _stop(true){}static void *threadroutine(void *args) // 类成员函数,形参是有this指针的!!{Thread<T> *self = static_cast<Thread<T> *>(args);self->Excute();return nullptr;}bool Start(){int n = pthread_create(&_tid, nullptr, threadroutine, this);if (!n){_stop = false;return true;}else{return false;}}void Detach(){if (!_stop){pthread_detach(_tid);}}void Join(){if (!_stop){pthread_join(_tid, nullptr);}}std::string name(){return _threadname;}void Stop(){_stop = true;}~Thread() {}private:pthread_t _tid;std::string _threadname;T &_data; // 为了让所有的线程访问同一个全局变量func_t<T> _func;bool _stop;};
}#endif
Task.hpp 代码:
#pragma once#include<iostream>
#include<functional>using Task=std::function<void()>;void Download()
{std::cout<<"this is a download task"<<std::endl;
}
RingQueue.hpp 代码:
#pragma once#include <iostream>
#include <string>
#include <semaphore.h>
#include <pthread.h>
#include <vector>template <typename T>
class RingQueue
{
private:void P(sem_t &sem){sem_wait(&sem); // 获取信号量,获取成功时,信号量的值减少,有一个资源被占用}void V(sem_t &sem){sem_post(&sem); // 增加信号量}void Lock(pthread_mutex_t &mutex){pthread_mutex_lock(&mutex);}void Unlock(pthread_mutex_t &mutex){pthread_mutex_unlock(&mutex);}public:RingQueue(int cap): _cap(cap), _consumer_step(0), _productor_step(0), _ring_queue(cap){sem_init(&_room_sem, 0, _cap); // 空间的信号量初始值为 _capsem_init(&_data_sem, 0, 0);pthread_mutex_init(&_consumer_mutex, nullptr);pthread_mutex_init(&_productor_mutex, nullptr);}void Enqueue(const T &in){P(_room_sem); // 入环形队列时,先申请空间信号量,占空间Lock(_productor_mutex);//因为 _productor_step 是临界资源,需要加锁保护临界资源// 既拿到锁了,也有空间,就可以生产_ring_queue[_productor_step++] = in; // 生产_productor_step %= _cap;Unlock(_productor_mutex);V(_data_sem); // 放入一个数据,数据信号量增加}void Pop(T *out){P(_data_sem);Lock(_consumer_mutex);//因为 _consumer_step 是临界资源,需要加锁保护临界资源*out = _ring_queue[_consumer_step++];_consumer_step %= _cap;Unlock(_consumer_mutex);V(_room_sem); // 空出一个空间}~RingQueue(){sem_destroy(&_data_sem);sem_destroy(&_room_sem);pthread_mutex_destroy(&_consumer_mutex);pthread_mutex_destroy(&_productor_mutex);}private:std::vector<T> _ring_queue; // 用数组模拟环形队列int _cap; // 环形队列的上限// 访问队列的下标int _consumer_step;int _productor_step;// 信号量sem_t _room_sem;sem_t _data_sem;// 互斥锁pthread_mutex_t _consumer_mutex;pthread_mutex_t _productor_mutex;
};
test.cc 代码:
#include "Task.hpp"
#include "Thread.hpp"
#include "RingQueue.hpp"#include <string>
#include <vector>
#include <unistd.h>using namespace ThreadModule;
// 给类填好模板,并重命名
using ringqueue_t = RingQueue<Task>;// 创建线程
void InitComm(std::vector<Thread<ringqueue_t>> *threads, int num, ringqueue_t &rq, func_t<ringqueue_t> func, std::string who)
{for (int i = 0; i < num; i++){std::string name = "thread-" + std::to_string(i + 1) + "-" + who;threads->emplace_back(func, rq, name);}
}void Consumer(ringqueue_t &rq, std::string name)
{while (true){sleep(2);Task t;rq.Pop(&t); // 取出任务t(); // 处理任务std::cout << "Consumer: [ " << name << " ]" << std::endl;}
}void Productor(ringqueue_t &rq, std::string name)
{// 开始生产while (true){rq.Enqueue(Download); // 放入任务std::cout << "Productor: [ " << name << " ]" << std::endl;}
}
void StartAll(std::vector<Thread<ringqueue_t>> &threads)
{for (auto &thread : threads){std::cout << "start: " << thread.name() << std::endl;thread.Start();}
}
void InitProductor(std::vector<Thread<ringqueue_t>> *threads, int num, ringqueue_t &rq)
{InitComm(threads, num, rq, Productor, "Productor");
}void InitConsumer(std::vector<Thread<ringqueue_t>> *threads, int num, ringqueue_t &rq)
{InitComm(threads, num, rq, Consumer, "Consumer");
}void WaitAll(std::vector<Thread<ringqueue_t>> &threads)
{for (auto &thread : threads){thread.Join();}
}
int main()
{// new 调用构造函数,创建一个上限为 10 的环形队列ringqueue_t *rq = new ringqueue_t(10);// 给线程传任务所需要的参数,并把线程放入数组管理起来std::vector<Thread<ringqueue_t>> threads;InitProductor(&threads, 3, *rq);InitConsumer(&threads, 5, *rq);StartAll(threads);WaitAll(threads);return 0;
}
结果:
每次运行的结果都不一样:

相关文章:
Linux -- 生产消费模型之环形队列、信号量
目录 前言 环形队列 如何用环形队列实现生产消费模型? 信号量 sem_t sem_init(初始化信号量) sem_destroy(销毁信号量) 什么是PV操作? sem_wait(P操作,减少信号量ÿ…...
Ashy的考研游记
文章目录 摘要12.1112.2012.21 DAY1(政治/英语)政治英语 12.22 DAY2(数学/专业课)数学专业课 结束估分 摘要 在24年的12月里,Ashy完成了他的考研冲刺,顺利的结束了他本年度的考研之旅。 在十二月里&#…...
MySQL线上事故:使用`WHERE`条件`!=xxx`无法查询到NULL数据
前言 在一次 MySQL 的线上查询操作中,因为 ! 的特性导致未能正确查询到为 NULL 的数据,险些引发严重后果。本文将详细解析 NULL 在 SQL 中的行为,如何避免类似问题,并提供实际操作建议。 1. 为什么NULL会查询不到? 在…...
vue3学习笔记(11)-组件通信
1.props 父传子 子传夫 父传子 接收用defineProps([]) 空字符串也是假 2.自定义事件 $event:事件对象 ref定义的数据在模板里面引用的时候可以不用.value 3.子传父 宏函数 触发事件 声明事件 defineEmits() 挂载之后3s钟触发 4.命名 肉串命名 5.任意组件通信 mitt pubs…...
【PDF物流单据提取明细】批量PDF提取多个区域内容导出表格或用区域内容对文件改名,批量提取PDF物流单据单号及明细导出表格并改名的技术难点及小节
相关阅读及下载: PDF电子物流单据: 批量PDF提取多个区域局部内容重命名PDF或者将PDF多个局部内容导出表格,具体使用步骤教程和实际应用场景的说明演示https://mp.weixin.qq.com/s/uCvqHAzKglfr40YPO_SyNg?token720634989&langzh_CN扫描…...
张量与数据类型
Pytorch最基本的操作对象——张量(tensor),张量是Pytorch中重要的数据结构,可认为是一个高维数组。一般的,标量(scalar)是只有大小没有方向的量,如1、2、3等;向量&#x…...
torchvision.utils.make_grid 解释下
torchvision.utils.make_grid 是 PyTorch 中 torchvision 库提供的一个实用函数,用于将多个图像拼接成一个网格,方便进行可视化。 主要功能 make_grid 将一批图片组织成一个网格形式,输出一个单一的张量,便于使用可视化工具(如 Matplotlib)查看图像。 参数解释 torchvi…...
Android原生Widget使用步骤
需要创建三个XML文件以及一个Class文件 三个XML文件分别是 Widget布局文件 <?xml version"1.0" encoding"utf-8"?> <RelativeLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_p…...
实验八 指针2
7-1 利用指针返回多个函数值 分数 30 全屏浏览 切换布局 作者 陈晓梅 单位 广东外语外贸大学 读入n个整数,调用max_min()函数求这n个数中的最大值和最小值。 输入格式: 输入有两行: 第一行是n值; 第二行是n个数。 输出格式: 输出最大…...
1 数据库(下):多表设计 、多表查询 + SQL中的with查询语法(MySQL8.0以后版本才支持这种新语法)+ 数据库优化(索引优化)
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、多表设计1 多表设计-概述2 三种多表关系一对多(多对一)(1)无外键约束(逻辑外键)&…...
什么是.net framework,什么是.net core,什么是.net5~8,版本对应关系
我不知道有多少人和我一样,没学习过.netCore,想要学习,但是版本号太多就蒙了,不知道学什么了,这里解释下各个版本的关系 我们一般开始学习微软的时候,都是开始学习的.netframework,常用的就是4…...
vulhub-wordpress靶场
一.主题上传漏洞 来到靶场点击主题选择add new 这里有一个上传主题的地方 我们可以去网上找到wordpress主题下载一个 wordpress模板 网页设计模板 免费 免费下载 - 爱给网 下载完成后对我们有用的东西只有这一个目录,把它拖出来 点开moban目录后,创建…...
安装与配置
《PHP Libxml》是一个在PHP中处理XML和HTML文档的重要库。它提供了丰富的API,支持DOM、SimpleXML和XMLReader等多种解析方式,广泛应用于各种编程语言和项目中。 安装与配置 安装: 在PHP中,libxml扩展通常是默认启用的。如果你需要手动安装&…...
斗鱼Android面试题及参考答案
常用的图片框架有哪些? Glide:是一个快速高效的 Android 图片加载库,专注于平滑滚动。它支持多种图片格式,包括 GIF,具有高效的缓存策略,能自动管理图片的生命周期,避免内存泄漏和 OOM 错误。其 API 简洁易用,可轻松实现图片的加载、显示和缓存等功能,如一行代码即可实…...
Could not install Gradle distribution from 的解决办法
在安装完成AndroidStudio之后,运行工程出现如下错误 Could not install Gradle distribution from https://services.gradle.org/distributions/gradle-6.5-bin.zip. 错误原因是:对应版本的Gradle文件下载失败了,我这里是gradle-6.5-bin.zip,不同版本的android studio也可…...
基于 SensitiveWordBs 实现敏感词过滤功能
在现代的互联网应用中,敏感词过滤已成为一个必不可少的功能,尤其是在社交媒体、评论审核等需要保证内容健康的场景下。本文将基于开源库https://github.com/houbb/sensitive-word,详细讲解如何通过自定义敏感词库和工具类实现高效的敏感词过滤…...
网络安全威胁2024年中报告
下载地址: 网络安全威胁2024年中报告-奇安信...
批次特征组杂记
批次特征组杂记 运维的时候新增了一个批次特征,然后发现不能按照要求跑到之前已经分好的批次特征组。 研究了半天原来是通过布局实现的。 特此记录。...
【HarmonyOS】解决自定义弹框和键盘之间安全距离的问题
【HarmonyOS】解决自定义弹框和键盘之间安全距离的问题 一、问题背景 我们在应用开发评论输入框时,常规的需求样式是:输入框view和键盘贴近,上半部展示信息区的形式,这样的设计,方便用户不割裂的去评论发言。 但是在…...
如何在LabVIEW中更好地使用ActiveX控件?
在LabVIEW中,ActiveX控件可以帮助实现与其他应用程序或第三方组件的集成(例如Microsoft Excel、Word、Internet Explorer等)。以下是一些建议,帮助您更好地在LabVIEW中使用ActiveX控件: 1. 理解ActiveX控件的基本原…...
以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:
一、属性动画概述NETX 作用:实现组件通用属性的渐变过渡效果,提升用户体验。支持属性:width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项: 布局类属性(如宽高)变化时&#…...
React Native在HarmonyOS 5.0阅读类应用开发中的实践
一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强,React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 (1)使用React Native…...
AI编程--插件对比分析:CodeRider、GitHub Copilot及其他
AI编程插件对比分析:CodeRider、GitHub Copilot及其他 随着人工智能技术的快速发展,AI编程插件已成为提升开发者生产力的重要工具。CodeRider和GitHub Copilot作为市场上的领先者,分别以其独特的特性和生态系统吸引了大量开发者。本文将从功…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
.Net Framework 4/C# 关键字(非常用,持续更新...)
一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...
JAVA后端开发——多租户
数据隔离是多租户系统中的核心概念,确保一个租户(在这个系统中可能是一个公司或一个独立的客户)的数据对其他租户是不可见的。在 RuoYi 框架(您当前项目所使用的基础框架)中,这通常是通过在数据表中增加一个…...
算法岗面试经验分享-大模型篇
文章目录 A 基础语言模型A.1 TransformerA.2 Bert B 大语言模型结构B.1 GPTB.2 LLamaB.3 ChatGLMB.4 Qwen C 大语言模型微调C.1 Fine-tuningC.2 Adapter-tuningC.3 Prefix-tuningC.4 P-tuningC.5 LoRA A 基础语言模型 A.1 Transformer (1)资源 论文&a…...
基于Java+VUE+MariaDB实现(Web)仿小米商城
仿小米商城 环境安装 nodejs maven JDK11 运行 mvn clean install -DskipTestscd adminmvn spring-boot:runcd ../webmvn spring-boot:runcd ../xiaomi-store-admin-vuenpm installnpm run servecd ../xiaomi-store-vuenpm installnpm run serve 注意:运行前…...
MySQL的pymysql操作
本章是MySQL的最后一章,MySQL到此完结,下一站Hadoop!!! 这章很简单,完整代码在最后,详细讲解之前python课程里面也有,感兴趣的可以往前找一下 一、查询操作 我们需要打开pycharm …...
spring Security对RBAC及其ABAC的支持使用
RBAC (基于角色的访问控制) RBAC (Role-Based Access Control) 是 Spring Security 中最常用的权限模型,它将权限分配给角色,再将角色分配给用户。 RBAC 核心实现 1. 数据库设计 users roles permissions ------- ------…...



