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控件的基本原…...
Cursor实现用excel数据填充word模版的方法
cursor主页:https://www.cursor.com/ 任务目标:把excel格式的数据里的单元格,按照某一个固定模版填充到word中 文章目录 注意事项逐步生成程序1. 确定格式2. 调试程序 注意事项 直接给一个excel文件和最终呈现的word文件的示例,…...
新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案
随着新能源汽车的快速普及,充电桩作为核心配套设施,其安全性与可靠性备受关注。然而,在高温、高负荷运行环境下,充电桩的散热问题与消防安全隐患日益凸显,成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...
C++八股 —— 单例模式
文章目录 1. 基本概念2. 设计要点3. 实现方式4. 详解懒汉模式 1. 基本概念 线程安全(Thread Safety) 线程安全是指在多线程环境下,某个函数、类或代码片段能够被多个线程同时调用时,仍能保证数据的一致性和逻辑的正确性…...
有限自动机到正规文法转换器v1.0
1 项目简介 这是一个功能强大的有限自动机(Finite Automaton, FA)到正规文法(Regular Grammar)转换器,它配备了一个直观且完整的图形用户界面,使用户能够轻松地进行操作和观察。该程序基于编译原理中的经典…...
【从零学习JVM|第三篇】类的生命周期(高频面试题)
前言: 在Java编程中,类的生命周期是指类从被加载到内存中开始,到被卸载出内存为止的整个过程。了解类的生命周期对于理解Java程序的运行机制以及性能优化非常重要。本文会深入探寻类的生命周期,让读者对此有深刻印象。 目录 …...
【Linux】Linux 系统默认的目录及作用说明
博主介绍:✌全网粉丝23W,CSDN博客专家、Java领域优质创作者,掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围:SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…...
快刀集(1): 一刀斩断视频片头广告
一刀流:用一个简单脚本,秒杀视频片头广告,还你清爽观影体验。 1. 引子 作为一个爱生活、爱学习、爱收藏高清资源的老码农,平时写代码之余看看电影、补补片,是再正常不过的事。 电影嘛,要沉浸,…...
rm视觉学习1-自瞄部分
首先先感谢中南大学的开源,提供了很全面的思路,减少了很多基础性的开发研究 我看的阅读的是中南大学FYT战队开源视觉代码 链接:https://github.com/CSU-FYT-Vision/FYT2024_vision.git 1.框架: 代码框架结构:readme有…...
MySQL体系架构解析(三):MySQL目录与启动配置全解析
MySQL中的目录和文件 bin目录 在 MySQL 的安装目录下有一个特别重要的 bin 目录,这个目录下存放着许多可执行文件。与其他系统的可执行文件类似,这些可执行文件都是与服务器和客户端程序相关的。 启动MySQL服务器程序 在 UNIX 系统中,用…...
13.10 LangGraph多轮对话系统实战:Ollama私有部署+情感识别优化全解析
LangGraph多轮对话系统实战:Ollama私有部署+情感识别优化全解析 LanguageMentor 对话式训练系统架构与实现 关键词:多轮对话系统设计、场景化提示工程、情感识别优化、LangGraph 状态管理、Ollama 私有化部署 1. 对话训练系统技术架构 采用四层架构实现高扩展性的对话训练…...



