【Linux】基于环形队列的生产者消费者模型的实现

这篇具有很好参考价值的文章主要介绍了【Linux】基于环形队列的生产者消费者模型的实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

文章目录

  • 前言
  • 一、基于环形队列的生产者消费者模型的实现

前言

上一篇文章我们讲了信号量的几个接口和基于环形队列的生产者消费者模型,下面我们就快速来实现。


一、基于环形队列的生产者消费者模型的实现

首先我们创建三个文件,分别是makefile,RingQueue.hpp,以及main.cc。我们先简单搭建一下环形队列的框架:

#pragma once
#include <iostream>
#include <vector>
#include <semaphore.h>

static const int gcap = 5;

template <class T>
class RingQueue
{
public:
    RingQueue(const int& cap = gcap)
        :_queue(_cap)
        ,_cap(cap)
    {

    }
private:
    std::vector<T> _queue;
    int _cap;    //表示环形队列的容量
    sem_t _spaceSem;  //   代表空间资源的信号量
    sem_t _dataSem;   //   代表数据资源的信号量
};

首先我们的环形队列为了能存放任意类型的数据,所以直接用了模板参数。然后我们的环形队列的底层是vector,我们用cap这个变量表示环形队列的容量,然后我们之前提到过生产者关心的是空间资源,消费者关心的是数据资源,所以我们有两个信号量来分别表示,使用信号量的头文件是

semaphore.h,然后我们看看信号量初始化的接口:

【Linux】基于环形队列的生产者消费者模型的实现,linux,c++,linux,环形队列,信号量,生产者与消费者模型,PV操作,vscode

 可以看到信号量初始化的接口有三个参数,我们都在上一篇文章讲过,pshared代表是否要共享(0就是线程间共享,非0就是进程间共享),Value是信号量的初始值。这个接口返回0代表成功,-1说明失败。

所以我们就可以对信号量这样初始化:

RingQueue(const int& cap = gcap)
        :_queue(cap)
        ,_cap(cap)
    {
        int n = sem_init(&_spaceSem,0,_cap);
        if (n==-1)
        {
            cout<<"_spaceSem信号量异常"<<endl;
        }
        n = sem_init(&_dataSem,0,0);
        if (n==-1)
        {
            cout<<"_dataSem信号量异常"<<endl;
        }
    }

我们一开始空间信号量肯定是队列空间容量,因为该开始没有数据占用空间。而开始的时候生产者并没有生产所以数据的信号量为0.

下面我们再写析构函数:

当我们程序退出的时候,要提前释放信号量的资源,所以我们看一下sem_destroy接口:

【Linux】基于环形队列的生产者消费者模型的实现,linux,c++,linux,环形队列,信号量,生产者与消费者模型,PV操作,vscode

 ~RingQueue()
    {
        int n = sem_destroy(&_spaceSem);
        if (n==-1) cout<<"sem_destroy(&_spaceSem)失败"<<endl;
        n = sem_destroy(&_dataSem);
        if (n==-1) cout<<"sem_destroy(&_dataSem)失败"<<endl;
    }

写好了环形队列的框架后,我们再大致写一下main.cc的代码:

#include "RingQueue.hpp"
#include <pthread.h>

void *ProductorRoutine(void* rq)
{
     RingQueue<int>* ringq = static_cast<RingQueue<int>*>(rq);
     while (true)
     {
        //
     }
}
void *ConsumerRoutine(void* rq)
{
     RingQueue<int>* ringq = static_cast<RingQueue<int>*>(rq);
     while (true)
     {
        //
     }
}

int main()
{
    RingQueue<int>* rq = new RingQueue<int>();

    pthread_t p,c;
    pthread_create(&p,nullptr,ProductorRoutine,rq);
    pthread_create(&c,nullptr,ConsumerRoutine,rq);

    pthread_join(p,nullptr);
    pthread_join(c,nullptr);
    delete rq;
    return 0;
}

首先我们创建两个线程,一个代表生产者,一个代表消费者。我们让其线程调用回调函数的时候传入我们写的环形队列,然后结束前让主线程等待两个线程,最后释放掉环形队列的资源。两个回调函数分别是为生产者和消费者服务的,我们用安全的类型转换接收一开始传过来的环形队列指针,然后我们继续编写环形队列的代码。

现在我们需要考虑如何从环形队列中安全的拿出数据,所以我们需要有一个Push接口:

    void Push(const T& in)
    {

    }
    void pop(T* out)
    {
        
    }

当我们想要写push接口的时候才发现,我们生产的时候还要让信号量++--,所以我们还需要有P操作和V操作的接口:

【Linux】基于环形队列的生产者消费者模型的实现,linux,c++,linux,环形队列,信号量,生产者与消费者模型,PV操作,vscode

 再写这两个接口前我们还需要认识sem_wait接口:

【Linux】基于环形队列的生产者消费者模型的实现,linux,c++,linux,环形队列,信号量,生产者与消费者模型,PV操作,vscode

 这个接口可以让我们的信号量-1,对应的是P操作。

【Linux】基于环形队列的生产者消费者模型的实现,linux,c++,linux,环形队列,信号量,生产者与消费者模型,PV操作,vscode

 sem_post接口可以让信号量+1,代表V操作。

    void P(sem_t& sem)
    {
       int n = sem_wait(&sem);
       if (n==-1) cout<<"sem_wait失败"<<endl;
    }
    void V(sem_t& sem)
    {
       int n = sem_post(&sem);
       if (n==-1) cout<<"sem_wait失败"<<endl;
    }

下面我们开始写push和pop接口:

我们每次生产都会有一个位置,所以我们直接用一个变量来表示生产者的位置,再用另一个变量表示消费者的位置。

【Linux】基于环形队列的生产者消费者模型的实现,linux,c++,linux,环形队列,信号量,生产者与消费者模型,PV操作,vscode

【Linux】基于环形队列的生产者消费者模型的实现,linux,c++,linux,环形队列,信号量,生产者与消费者模型,PV操作,vscode

    void Push(const T& in)
    {
        //生产者生产前需要预定空间
        P(_spaceSem);
        _queue[productorStep++] = in;
        productorStep%=_cap;
        //生产完成后数据多了一个
        V(_dataSem);
    }

我们刚开始生产的时候需要预定空间,所以我们要对空间信号量做P操作,然后在队列中生产者的位置放上生产的数据,这里让productorStep后置++就找到了下一次的位置。然后我们要保证生产者的位置不越界所以每次都要%上队列的容量,然后我们生产完成后数据就变多了,所以需要对数据进行V操作。

    void pop(T* out)
    {
        //消费前需要预定消费资源
        P(_dataSem);
        *out = _queue[consumerStep++];
        consumerStep%=_cap;
        //消费后空间变多
        V(_spaceSem);

    }

消费者消费前同样要看有没有数据资源,如果没有就阻塞在P操作,如果有就将消费的资源给out,然后为了防止消费者的位置越界需要%cap,并且消费者消费完成后空间会多出来。

下面我们完成以下main函数中的代码:

【Linux】基于环形队列的生产者消费者模型的实现,linux,c++,linux,环形队列,信号量,生产者与消费者模型,PV操作,vscode

 首先我们种一个随机数种子,为了防止数据重复我们^了pid和线程ID,然后我们的任务就是简单的数字:

void *ProductorRoutine(void* rq)
{
     RingQueue<int>* ringq = static_cast<RingQueue<int>*>(rq);
     while (true)
     {
        int data = rand()%10 + 1;
        ringq->Push(data);
        std::cout<<"生产完成,生产的数据是: "<<data<<std::endl;
     }
}
void *ConsumerRoutine(void* rq)
{
     RingQueue<int>* ringq = static_cast<RingQueue<int>*>(rq);
     while (true)
     {
        int data;
        ringq->pop(&data);
        std::cout<<"消费完成,消费的数据是: "<<data<<std::endl;
     }
}

生产一个随机数然后push到我们的环形队列,然后打印生产完成。消费的时候直接去循环队列那数据然后打印即可,下面我们运行起来:

【Linux】基于环形队列的生产者消费者模型的实现,linux,c++,linux,环形队列,信号量,生产者与消费者模型,PV操作,vscode

 运行后我们可以看到是没有问题的,当然我们也可以生产前sleep()一下,不然运行太快了。

【Linux】基于环形队列的生产者消费者模型的实现,linux,c++,linux,环形队列,信号量,生产者与消费者模型,PV操作,vscode

【Linux】基于环形队列的生产者消费者模型的实现,linux,c++,linux,环形队列,信号量,生产者与消费者模型,PV操作,vscode 下面我们将上次写的生产任务的文件复制过来,然后让这个环形队列去完成加减乘除的任务。

下面是任务的代码:

class Task
{
public:
    Task()
    {

    }
    Task(int x,int y,char op)
       :_x(x)
       ,_y(y)
       ,_op(op)
       ,_result(0)
       ,_exitCode(0)
    {

    }
    void operator()()
    {
       switch(_op)
       {
       case '+':
           _result = _x + _y;
           break;
       case '-':
           _result = _x - _y;
           break;
       case '*':
           _result = _x * _y;
           break;
       case '/':
           if (_y==0)
           {
              _exitCode = -1;
           }
           else 
           {
              _result = _x / _y;
           }
           break;
       case '%':
           if (_y==0)
           {
              _exitCode = -2;
           }
           else 
           {
              _result = _x % _y;
           }
           break;
       default:
           break;
       }
    }
    std::string formatArg()
    {
        return std::to_string(_x) +_op +std::to_string(_y) + "=";
    }
    std::string formatRes()
    {
        return std::to_string(_result) + "(" + std::to_string(_exitCode) + ")";
    }
    ~Task()
    {

    }
private:
    int _x;
    int _y;
    char _op;
    int _result;
    int _exitCode;
};

 思路还是和我们阻塞队列的时候一样,直接将环形队列的类型改为Task即可:

【Linux】基于环形队列的生产者消费者模型的实现,linux,c++,linux,环形队列,信号量,生产者与消费者模型,PV操作,vscode

【Linux】基于环形队列的生产者消费者模型的实现,linux,c++,linux,环形队列,信号量,生产者与消费者模型,PV操作,vscode 【Linux】基于环形队列的生产者消费者模型的实现,linux,c++,linux,环形队列,信号量,生产者与消费者模型,PV操作,vscode

 注意我们在消费者的函数中调用仿函数()可以直接完成加减乘除的运算。

下面我们运行起来看看效果:

【Linux】基于环形队列的生产者消费者模型的实现,linux,c++,linux,环形队列,信号量,生产者与消费者模型,PV操作,vscode

 可以看到程序运行是没问题的,每个任务都被完成了。

刚刚我们思考的是单生产单消费模型,下面我们思考一下如果改动代码实现多生产多消费模型呢?

其实很简单,我们的消费者和生产者是完全独立的,也就是说生产者不生产只要队列有数据消费者依旧可以消费。即使消费者不消费,只要队列里有空间,那么生产者就可以生产,既然他们两个不构成互斥关系,那么我们就可以定义两把锁,一把锁去锁生产者,另一把锁去锁消费者。

【Linux】基于环形队列的生产者消费者模型的实现,linux,c++,linux,环形队列,信号量,生产者与消费者模型,PV操作,vscode

【Linux】基于环形队列的生产者消费者模型的实现,linux,c++,linux,环形队列,信号量,生产者与消费者模型,PV操作,vscode 【Linux】基于环形队列的生产者消费者模型的实现,linux,c++,linux,环形队列,信号量,生产者与消费者模型,PV操作,vscode

 下面我们就直接在Push和Pop接口中加锁了:

【Linux】基于环形队列的生产者消费者模型的实现,linux,c++,linux,环形队列,信号量,生产者与消费者模型,PV操作,vscode

 然后我们创建多个线程:

int main()
{
    srand((unsigned int)time(nullptr)^getpid()^pthread_self());
    RingQueue<Task>* rq = new RingQueue<Task>();
    pthread_t p[4],c[8];
    for (int i = 0;i<4;i++)
    {
        pthread_create(p+i,nullptr,ProductorRoutine,rq);
    }
    for (int i = 0;i<8;i++)
    {
        pthread_create(c+i,nullptr,ProductorRoutine,rq);
    }
    for (int i = 0;i<4;i++)
    {
        pthread_join(p[i],nullptr);
    }
    for (int i = 0;i<8;i++)
    {
        pthread_join(c[i],nullptr);
    }
    delete rq;
    return 0;
}

【Linux】基于环形队列的生产者消费者模型的实现,linux,c++,linux,环形队列,信号量,生产者与消费者模型,PV操作,vscode

 可以看到我们确实完成了多生产多消费的例子。但是我们目前的代码还有一些小问题,实际上我们的信号量本来就是原子的不需要加锁操作,我们正确的写法应该是下面这样:

    void Push(const T& in)
    {
        //生产者生产前需要预定空间
        P(_spaceSem);
        pthread_mutex_lock(&_pmutex);
        _queue[productorStep++] = in;
        productorStep%=_cap;
        pthread_mutex_unlock(&_pmutex);
        //生产完成后数据多了一个
        V(_dataSem);
    }
    void pop(T* out)
    {
        //消费前需要预定消费资源
        P(_dataSem);
        pthread_mutex_lock(&_cmutex);
        *out = _queue[consumerStep++];
        consumerStep%=_cap;
        pthread_mutex_unlock(&_cmutex);
        //消费后空间变多
        V(_spaceSem);
    }

以上就是基于环形队列实现生产者消费者模型的全部内容了,这部分的代码很容易写错,一不小心就会出问题,所以大家写代码的时候一定要细心,而且linux环境下不太好调试。文章来源地址https://www.toymoban.com/news/detail-516553.html

到了这里,关于【Linux】基于环形队列的生产者消费者模型的实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Linux学习】多线程——同步 | 条件变量 | 基于阻塞队列的生产者消费者模型

    🐱作者:一只大喵咪1201 🐱专栏:《Linux学习》 🔥格言: 你只管努力,剩下的交给时间! 以生活中消费者生产者为例: 生活中,我们大部分人都扮演着消费者的角色,会经常在超市买东西,比如买方便面,而超市的方便面是由供应商生成的。所以我们就是消费者,供应商

    2024年02月05日
    浏览(37)
  • 基于 BlockQueue(阻塞队列) 的 生产者消费者模型

    阻塞队列(Blocking Queue) 是一种特殊类型的队列,它具有阻塞操作的特性。在并发编程中,阻塞队列可以用于实现线程间的安全通信和数据共享。 阻塞队列的 主要特点 是: 当 队列为空时 ,消费者线程尝试从队列中获取(出队)元素时会被阻塞,直到有新的元素被添加到队

    2024年02月12日
    浏览(41)
  • Linux之信号量 | 消费者生产者模型的循环队列

    目录 一、信号量 1、概念 2、信号量操作函数 二、基于环形队列的生产者消费者模型 1、模型分析 2、代码实现 1、单生产单消费的生产者消费者模型 2、多生产多消费的生产者消费者模型 引入:前面我们讲到了,对临界资源进行访问时,为了保证数据的一致性,我们需要对临

    2024年04月17日
    浏览(29)
  • Linux之【多线程】生产者与消费者模型&BlockQueue(阻塞队列)

    举个例子:学生要买东西,一般情况下都会直接联系厂商,因为买的商品不多,对于供货商来说交易成本太高,所以有了交易场所超市这个媒介的存在。目的就是为了集中需求,分发产品。 消费者与生产者之间通过了超市进行交易。当生产者不需要的时候,厂商可以继续生产

    2024年02月02日
    浏览(28)
  • 【Java系列】多线程案例学习——基于阻塞队列实现生产者消费者模型

    个人主页:兜里有颗棉花糖 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 兜里有颗棉花糖 原创 收录于专栏【Java系列专栏】【JaveEE学习专栏】 本专栏旨在分享学习JavaEE的一点学习心得,欢迎大家在评论区交流讨论💌 什么是阻塞式队列(有两点): 第一点:当队列满的时候

    2024年02月04日
    浏览(39)
  • 【linux】线程同步+基于BlockingQueue的生产者消费者模型

    喜欢的点赞,收藏,关注一下把! 在线程互斥写了一份抢票的代码,我们发现虽然加锁解决了抢到负数票的问题,但是一直都是一个线程在抢票,它错了吗,它没错但是不合理。那我们应该如何安全合理的抢票呢? 讲个小故事。 假设学校有一个VIP学霸自习室,这个自习室有

    2024年02月03日
    浏览(89)
  • Python多线程Thread——生产者消费者模型 python队列与多线程——生产者消费者模型

    下面面向对象的角度看线程 那么你可以试试看能不能用面向对象的方法实现生产者消费者模型吧。

    2024年02月09日
    浏览(44)
  • 多线程(初阶七:阻塞队列和生产者消费者模型)

    目录 一、阻塞队列的简单介绍 二、生产者消费者模型 1、举个栗子: 2、引入生产者消费者模型的意义: (1)解耦合 (2)削峰填谷 三、模拟实现阻塞队列 1、阻塞队列的简单介绍 2、实现阻塞队列 (1)实现普通队列 (2)加上线程安全 (3)加上阻塞功能 3、运用阻塞队列

    2024年02月05日
    浏览(31)
  • 多线程学习之生产者和消费者与阻塞队列的关系

    生产者消费者问题,实际上主要是包含了两类线程: 生产者线程用于生产数据 消费者线程用于消费数据 生产者和消费者之间通常会采用一个共享的数据区域,这样就可以将生产者和消费者进行解耦, 两者都不需要互相关注对方的 Object类的等待和唤醒方法 方法名 说明 void

    2024年02月11日
    浏览(30)
  • 优雅封装RabbitMQ实现动态队列、动态生产者,动态消费者绑定

    前言 SpringBoot 集成 RabbitMQ 公司老大觉得使用注解太繁琐了,而且不能动态生成队列所以让我研究是否可以动态绑定,所以就有了这个事情。打工人就是命苦没办法,硬着头皮直接就上了,接下来进入主题吧。 需求思路分析 根据老大的需求,大致分为使用配置文件进行配置,

    2024年02月16日
    浏览(28)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包