【线程】基于环形队列的生产者消费者模型

1 环形队列

环形队列采用数组来模拟,用取模运算来模拟环状特性。
在这里插入图片描述
1.如何判断环形队列为空或者为满?

  • 当环形队列为时,头和尾都指向同一个位置。
  • 当环形队列为时,头和尾也都指向同一个位置。

因此, 可以通过加计数器或者标记位来判满或者空,也可以预留一个空的位置,作为满的状态

1.1 生产者消费者中的环形队列

2.生产者和消费者什么时候会访问同一个位置?

1.环形队列为空的时候,生产者和消费者会访问同一个位置。
在这里插入图片描述
环形队列中,如果队列为空,那么队首和队尾的指针是指向同一个位置的。所以,生产者和消费者也会指向同一个位置。

2.环形队列为满的时候,生产者和消费者会访问同一个位置。
在这里插入图片描述
环形队列中,如果队列为满,那么队首和队尾的指针是指向同一个位置的。所以,生产者和消费者也会指向同一个位置。

其他任何时候,生产者和消费者访问的都是不同的区域。
在这里插入图片描述

3.环形队列中的数据需要注意什么?

1.消费者不能超过生产者。
在这里插入图片描述
也就是: 不能没有数据了还继续消费

2.生产者不能将消费者套圈。
在这里插入图片描述
也就是: 不能没有空间了还继续生产

2 设计信号量

生产者最在意的是空闲的空间个数
消费者最在意的是数据的个数

所以生产者每次在访问临界资源之前,需要先申请空间资源的信号量,申请成功就可以进行生产,否则就阻塞等待。

消费者在访问临界资源之前,需要申请数据资源的信号量,申请成功就可以消费数据,否则就阻塞等待。

空间资源信号量的申请由生产者进行,归还(V操作)由消费者进行,表示生产者可以生产数据。

数据资源信号量的申请有消费者进行,归还(V操作)由生产者进行,表示消费者可以进行消费.

4.空间资源信号如何设计?

最开始,生产者没有生产,所以空间资源是队列的大小(满的)

5.数据资源信号如何设计?

最开始,生产者没有生产,所以数据资源为0(空的)

2.1 生产者伪代码

productor_sem = 环形队列大小;

P(productor_sem);//申请空间资源信号量
//申请成功,继续向下运行。
//申请失败,阻塞在申请处。

.......//从事生产活动——把数据放入队列中

V(comsumer_sem);//归还数据资源信号量

2.2 消费者伪代码

comsumer_sem = 0;

P(comsumer_sem);//申请数据资源信号量
//申请成功,继续向下运行。
//申请失败,阻塞在申请处。

.......//从事消费活动——从队列中消费数据

V(proudctor_sem);//归还空间资源信号量

在环形队列中,大部分情况下单生产和单消费是可以并发执行的,只有在满或者空的时候,才会有同步和互斥问题,同步和互斥是通过信号量来实现的。

3 代码

3.1 RingQueue.h

#include <iostream>
#include <vector>
#include <semaphore.h>
#include <pthread.h>

//为什么基于阻塞队列的生产者消费者模型只需要一个锁 , 而基于环形队列的生产者消费者模型需要两个锁?
//1.因为阻塞队列是对同一个队列的同一个位置进行操作,队列是共享资源,需要对整个队列加锁
//2.阻塞队列中,生产者和消费者访问的不是同一个下标,所以两者是互不干扰的,只需要用条件变量来唤醒阻塞
//但是生产者对生产者而言,是需要加锁的。消费者对消费者而言,是需要加锁的。

template <class T>
class RingQueue
{
    static const int defaultnum = 5;
public:
    //p操作,申请信号量,信号量--
    void p(sem_t& sem)
    {
        sem_wait(&sem);
    }

    //v操作,释放信号量,信号量++
    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 = defaultnum)
    :_ringqueue(cap)
    ,_cap(cap)
    ,_c_step(0)
    ,_p_step(0)
    {
        //初始化信号量,给消费者初始的信号量为0,给生产者初始的信号量为cap(满)
        //因为最开始的时候没有商品,无法消费,只能生产
        sem_init(&_c_data_sem, 0, 0);
        sem_init(&_p_space_sem, 0, cap);

        pthread_mutex_init(&_c_mutex, nullptr);
        pthread_mutex_init(&_p_mutex, nullptr);
    }

    //生产商品
    void push(const T &in)
    {   
        //1.申请信号量
        p(_p_space_sem);
        //2.消费者加锁
        Lock(_p_mutex);

        //3.进行生产
        _ringqueue[_p_step] = in;
        _p_step++;
        _p_step %= _cap;  //保证下标一直都在环形队列里面

        //4.解锁
        UnLock(_p_mutex);
        //5.释放信号量
        v(_c_data_sem);
    }

    void pop(T* out)
    {
        //1.申请信号量
        p(_c_data_sem);
        //2.申请锁
        Lock(_c_mutex);

        //3.消费数据
        *out = _ringqueue[_c_step];
        ++_c_step;
        _c_step %= _cap;

        //4.解锁
        UnLock(_c_mutex);
        //5.生产者信号量++
        v(_p_space_sem);  //消费完数据之后,生产者的信号量++
    }
private:
    std::vector<T> _ringqueue;
    int _cap;     //capacity
    int _c_step;  //消费者在环形队列中的下标
    int _p_step;  //生产者在环形队列中的下标

    sem_t _c_data_sem;  //消费者关注的数据资源
    sem_t _p_space_sem; //生产者关注的消费资源

    pthread_mutex_t _c_mutex;   //消费者锁
    pthread_mutex_t _p_mutex;   //生产者锁
};

3.1.1 生产商品

//生产商品
    void push(const T &in)
    {   
        //1.申请信号量
        p(_p_space_sem);
        //2.消费者加锁
        Lock(_p_mutex);

        //3.进行生产
        _ringqueue[_p_step] = in;
        _p_step++;
        _p_step %= _cap;  //保证下标一直都在环形队列里面

        //4.解锁
        UnLock(_p_mutex);
        //5.释放信号量
        v(_c_data_sem);
    }

6.为什么要按照 申请信号量 → 加锁 → 解锁 → 释放信号量 的顺序来做?

如果先加锁再申请信号量,可能会出现以下这种情况:
加锁之后,发现没有空闲空间了,于是阻塞。而此时该线程还持有锁,就会导致死锁。.
如果先释放信号量再解锁,可能会出现以下这种情况:
释放信号量之后,消费者得知有可以消费的资源,于是开始消费。但是此时并没有解锁,因此生产者可能并没有完全完成生产,但是消费者已经开始消费。就会导致生产消费数据不一致的问题。.

3.1.2 消费商品

void pop(T* out)
    {
        //1.申请信号量
        p(_c_data_sem);
        //2.申请锁
        Lock(_c_mutex);

        //3.消费数据
        *out = _ringqueue[_c_step];
        ++_c_step;
        _c_step %= _cap;

        //4.解锁
        UnLock(_c_mutex);
        //5.生产者信号量++
        v(_p_space_sem);  //消费完数据之后,生产者的信号量++
    }

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

docker中运行的MySQL怎么修改密码

1&#xff0c;进入MySQL容器 docker exec -it 容器名 bash 我运行了 docker ps命令查看。正在运行的容器名称。可以看到MySQL的我起名为db docker exec -it db bash 这样就成功的进入到容器中了。 2&#xff0c;登录MySQL中 mysql -u 用户名 -p 回车 密码 mysql -u root -p roo…

SRS代码目录

代码目录&#xff1a; src/目录下核心代码&#xff1a; core&#xff1a;核心功能模块&#xff0c;包括日志、配置、错误处理等&#xff1b;protocol&#xff1a;实现RTMP、HTTP-FLV、HLS等协议的模块&#xff1b;app&#xff1a;应用层的实现&#xff0c;包括流的发布、播放…

Leetcode:680

1&#xff0c;题目 2&#xff0c;思路 首先就是判断它不发生改变会不会是回文如果不是回文&#xff0c;那么俩个指针从前往后与从后往前做对比如果俩字符不同&#xff0c;那就俩种选择&#xff0c;一种是保留前面的字符去掉后面字符&#xff0c;另一种是其反然后俩种选择只要满…

SliverAppBar的功能和用法

文章目录 1 概念介绍2 使用方法3 示例代码 我们在上一章回中介绍了SliverGrid组件相关的内容&#xff0c;本章回中将介绍SliverAppBar组件.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1 概念介绍 我们在本章回中介绍的SliverAppBar和普通的AppBar类似&#xff0c;它们的…

【前端】ES6模块化

文章目录 1. 模块化概述1.1 什么是模块化?1.2 为什么需要模块化? 2. 有哪些模块化规范3. CommonJs3.1 导出数据3.2 导入数据3.3 扩展理解3.4 在浏览器端运行 4.ES6模块化4.1 浏览器运行4.2 在node服务端运行4.3 导出4.3.1 分别导出4.3.2 统一导出4.3.3 默认导出4.3.4 混用 4.…

【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】2.16 记录数组:面向对象的数据操作

2.16 记录数组&#xff1a;面向对象的数据操作 内容提要 本文将深入探讨 NumPy 的 recarray 数据结构&#xff0c;这是一种特殊的数据类型&#xff0c;允许用户以面向对象的方式访问数组中的数据。我们首先介绍 recarray 的基本特性&#xff0c;然后讨论如何优化属性访问&…

本地搭建deepseek-r1

一、下载ollama(官网下载比较慢&#xff0c;可以找个网盘资源下) 二、安装ollama 三、打开cmd&#xff0c;拉取模型deepseek-r1:14b(根据显存大小选择模型大小&#xff09; ollama pull deepseek-r1:14b 四、运行模型 ollama run deepseek-r1:14b 五、使用网页api访问&#x…

linux本地部署deepseek-R1模型

国产开源大模型追平甚至超越了CloseAI的o1模型&#xff0c;大国崛起时刻&#xff01;&#xff01;&#xff01; DeepSeek R1 本地部署指南   在人工智能技术飞速发展的今天&#xff0c;本地部署AI模型成为越来越多开发者和企业关注的焦点。本文将详细介绍如何在本地部署DeepS…

手写MVVM框架-环境搭建

项目使用 webpack 进行进行构建&#xff0c;初始化步骤如下: 1.创建npm项目执行npm init 一直下一步就行 2.安装webpack、webpack-cli、webpack-dev-server&#xff0c;html-webpack-plugin npm i -D webpack webpack-cli webpack-dev-server html-webpack-plugin 3.配置webpac…

git基础使用--4---git分支和使用

文章目录 git基础使用--4---git分支和使用1. 按顺序看2. 什么是分支3. 分支的基本操作4. 分支的基本操作4.1 查看分支4.2 创建分支4.3 切换分支4.4 合并冲突 git基础使用–4—git分支和使用 1. 按顺序看 -git基础使用–1–版本控制的基本概念 -git基础使用–2–gti的基本概念…

想品客老师的第十天:类

类是一个优化js面向对象的工具 类的声明 //1、class User{}console.log(typeof User)//function//2、let Hdclass{}//其实跟1差不多class Stu{show(){}//注意这里不用加逗号&#xff0c;对象才加逗号get(){console.log(后盾人)}}let hdnew Stu()hd.get()//后盾人 类的原理 类…

JavaFX - 3D 形状

在前面的章节中&#xff0c;我们已经了解了如何在 JavaFX 应用程序中的 XY 平面上绘制 2D 形状。除了这些 2D 形状之外&#xff0c;我们还可以使用 JavaFX 绘制其他几个 3D 形状。 通常&#xff0c;3D 形状是可以在 XYZ 平面上绘制的几何图形。它们由两个或多个维度定义&#…

arm-linux-gnueabihf安装

Linaro Releases windows下打开wsl2中的ubuntu&#xff0c;资源管理器中输入&#xff1a; \\wsl$gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz 复制到/home/ark01/tool 在 Ubuntu 中创建目录&#xff1a; /usr/local/arm&#xff0c;命令如下&#xff1a; …

【双指针题目】

双指针 美丽区间&#xff08;滑动窗口&#xff09;合并数列&#xff08;双指针的应用&#xff09;等腰三角形全部所有的子序列 美丽区间&#xff08;滑动窗口&#xff09; 美丽区间 滑动窗口模板&#xff1a; int left 0, right 0;while (right < nums.size()) {// 增大…

【汽车电子软件架构】AutoSAR从放弃到入门专栏导读

本文是汽车电子软件架构&#xff1a;AutoSAR从放弃到入门专栏的导读篇。文章延续专栏文章的一贯作风&#xff0c;从概念与定义入手&#xff0c;希望读者能对AutoSAR架构有一个整体的认识&#xff0c;然后对专栏涉及的文章进行分类与链接。本文首先从AutoSAR汽车软件架构的概念&…

八、Spring Boot 日志详解

目录 一、日志的用途 二、日志使用 2.1 打印日志 2.1.1 在程序中获取日志对象 2.1.2 使用日志对象打印日志 2.2、日志框架介绍 2.2.1 门面模式(外观模式) 2.2.2 门面模式的实现 2.2.3 SLF4J 框架介绍 2.3 日志格式的说明 2.4 日志级别 2.4.1 日志级别的分类 2.4.2…

【Linux】24.进程信号(1)

文章目录 1. 信号入门1.1 进程与信号的相关知识1.2 技术应用角度的信号1.3 注意1.4 信号概念1.5 信号处理常见方式概览 2. 产生信号2.1 通过终端按键产生信号2.2 调用系统函数向进程发信号2.3 由软件条件产生信号2.4 硬件异常产生信号2.5 信号保存 3. 阻塞信号3.1 信号其他相关…

[Proteus仿真]基于51单片机的智能温控系统

[Proteus仿真]基于51单片机的智能温控系统 基于51单片机的智能温控系统&#xff1a;DS18B20精准测温LCD1602双屏显示三键设置上下限声光报警&#xff0c;支持温度校准、抗干扰设计、阈值记忆。 一.仿真原理图 ​​ 二.模块介绍 温度采集模块&#xff08;DS18B20&#xff0…

Windows下怎么安装FFFmpeg呢?

在Windows下使用Open-webui报错&#xff0c;说Couldnt find ffmpeg or avconv,解决open-webui报错Couldn‘t find ffmpeg or avconv-CSDN博客于是尝试解决问题&#xff0c;那么Windows下怎么安装FFFmpeg呢&#xff1f; 尝试了两种方法。 第一种方法pip安装&#xff08;失败&…

C基础寒假练习(2)

一、输出3-100以内的完美数&#xff0c;(完美数&#xff1a;因子和(因子不包含自身)数本身 #include <stdio.h>// 函数声明 int isPerfectNumber(int num);int main() {printf("3-100以内的完美数有:\n");for (int i 3; i < 100; i){if (isPerfectNumber…