Linux 操作系统:基于环形队列的生产者消费者模型

Linux 操作系统:基于环形队列的生产者消费者模型

一、前言

环形队列采用数组模拟,用模运算来模拟环状特性。和基于阻塞队列的生产者消费者模型不同的是,环形队列将公共资源分成多份使用,而阻塞队列则是将公共资源当作一个整体使用!!

二、大致框架

毫无疑问,我们首先需要一个数组来模拟环形队列,并且环形队列的大小也需指明!由于我们是将公共资源(即环形队列)分为多个小块单独使用,生产者向环形队列中插入数据,生产者向环形队列中取数据。这也意味着生产者和消费者的步数不一致,我们需要两个变量分别记录生产者和消费者的运动下标。

对于生产者来说,空间是资源;对于消费者来所,数据是资源。并且生产者不能把消费者套一个圈(此时队列已经为满);消费者不能超越生产者(此时队列已经为空)。由于环形结构起始状态和结束状态都是一样的,不好判断为空或者为,所以我们引入两把计数器分别表示公共资源的个数,即信号量!!(初始时,生产者空间资源为整个数组,消费者数据资源为0)

生产者消费者模型是多生产者多消费者间的消费模型。这也意味者可能存在多个生产者或多个消费者并发访问公共资源,会导致多执行流数据不一致问题!所以我们要为生产者和消费者各自维护一把锁!

【大致框架】:

c 复制代码
const int defaultSize = 5; //环形队列大小默认值

template <class T>
class RingQueue
{
public:
    RingQueue(int size = defaultSize)
        : _size(size), _ringqueue(size), _p_step(0), _c_step(0)
    {
        sem_init(&_data_sem, 0, 0);
        sem_init(&_space_sem, 0, size);
        pthread_mutex_init(&_mutex_p, nullptr);
        pthread_mutex_init(&_mutex_c, nullptr);
    }

    ~RingQueue()
    {
        sem_destroy(&_data_sem);
        sem_destroy(&_space_sem);

        pthread_mutex_destroy(&_mutex_c);
        pthread_mutex_destroy(&_mutex_p);
    }

private:
    std::vector<T> _ringqueue;
    int _size;

    int _p_step; // 生产者
    int _c_step; // 消费者

    sem_t _data_sem;  // 消费者使用
    sem_t _space_sem; // 生产者使用

    pthread_mutex_t _mutex_p; // 生产者使用
    pthread_mutex_t _mutex_c; // 消费者使用
};

二、P操作、V操作

由于后续生产者和消费者都需要进行P(申请信号量)、V(释放信号量)。所以我们在这对PV进行封装!

【具体如下】:

c 复制代码
 void P(sem_t &sem) // 申请信号量
 {
     sem_wait(&sem); // 等待信号量,等待成功会将信号量的值减1
 }

 void V(sem_t &sem) // 释放信号量
 {
     sem_post(&sem); // 发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1
 }

三、生产者生产数据

生产者要想环形队列中插入数据,首先需要P操作申请空间资源。一旦申请成功,就意味着完成了对空间资源的预定。换而言之环形队列还未满,还可以继续插入数据。为了防止多生产并发访问环形队列,记下来就是申请锁了。

只有两者都成功了,生产者才能向环形队列中Push数据。插入成功后,更新生产者步数下标即可!

【具体代码】:

c 复制代码
void Push(T &in)
{
    P(_space_sem);
    {
    	// LockGuard具体代码查看前言链接
        LockGuard lock(&_mutex_p);// RAII思想对锁进行了疯转,代替注释的显示加锁和解锁操作
        // pthread_mutex_lock(&_mutex_p);
        _ringqueue[_p_step] = in;
        _p_step++;
        _p_step %= _size;
        // pthread_mutex_unlock(&_mutex_p);
    }
    V(_data_sem);
}

四、生产者获取数据

我们给Pop函数传递一个输出型参数,将生产者需要的数据带出!

和消费行为一样,生产者首先需要生产空间资源(即空间信号量)、锁。然后将环形队列中的数据赋值个输出型产生,然后更新步数下标即可!

【具体代码】:

c 复制代码
void Pop(T *out)
{
    P(_data_sem);
    {
        // LockGuard具体代码查看前言链接
        LockGuard lock(&_mutex_c);// RAII思想对锁进行了疯转,代替注释的显示加锁和解锁操作
        //  pthread_mutex_lock(&_mutex_c);
        *out = _ringqueue[_c_step];
        _c_step++;
        _c_step %= _size;
        // pthread_mutex_unlock(&_mutex_c);
    }
    V(_space_sem);
}

五、代码测试

这里我们创建3个生产者和2个消费者。生产者插入的数据为2个随机数和随机运算符构造的任务Task;消费者直接获取任务执行!(消费者启动时先睡眠3秒,让生产者将环形队列填充满后在消费)

【具体代码】:

c 复制代码
void *Productor(void *args)
{
    RingQueue<Task> *rq = static_cast<RingQueue<Task> *>(args);
    while(true)
    {
        // sleep(1);
        int data_x = rand() % 10 + 1;
        usleep(1000);
        int data_y = rand() % 10 + 1;
        usleep(1000);
        char op = opers[rand() % opers.size()];
        Task t(data_x, data_y, op);
        rq->Push(t);
        std::cout << "Productor:" << t.PrintTask() << std::endl;
    }
}

void *Consumer(void *args)
{
    sleep(3);
    RingQueue<Task> *rq = static_cast<RingQueue<Task> *>(args);
    while(true)
    {
        sleep(1);
        Task t;
        rq->Pop(&t);
        t();
        std::cout << "Consumer: " << t.PrintResult() << std::endl;
    }
}


int main()
{
    srand(time(nullptr) ^ pthread_self() ^ getpid());
    RingQueue<Task> rq;

    pthread_t p[3], c[2];
    pthread_create(&p[0], nullptr, Productor, &rq);
    pthread_create(&p[1], nullptr, Productor, &rq);
    pthread_create(&p[2], nullptr, Productor, &rq);
    pthread_create(&c[0], nullptr, Consumer, &rq);
    pthread_create(&c[1], nullptr, Consumer, &rq);

    pthread_join(p[0], nullptr);
    pthread_join(p[1], nullptr);
    pthread_join(p[2], nullptr);
    pthread_join(c[0], nullptr);
    pthread_join(c[1], nullptr);
    return 0;
}

【运行结果】:

六、所有代码

gitee:基于环形队列的生产者消费者模型

相关推荐
饮啦冰美式10 分钟前
22.04Ubuntu---ROS2使用rclcpp编写节点
linux·运维·ubuntu
wowocpp10 分钟前
ubuntu 22.04 server 安装 和 初始化 LTS
linux·运维·ubuntu
Huaqiwill11 分钟前
Ubuntun搭建并行计算环境
linux·云计算
wclass-zhengge14 分钟前
Netty篇(入门编程)
java·linux·服务器
Lign1731415 分钟前
ubuntu unrar解压 中文文件名异常问题解决
linux·运维·ubuntu
vip4511 小时前
Linux 经典面试八股文
linux
大霞上仙1 小时前
Ubuntu系统电脑没有WiFi适配器
linux·运维·电脑
孤客网络科技工作室2 小时前
VMware 虚拟机使用教程及 Kali Linux 安装指南
linux·虚拟机·kali linux
颇有几分姿色3 小时前
深入理解 Linux 内存管理:free 命令详解
linux·运维·服务器
AndyFrank3 小时前
mac crontab 不能使用问题简记
linux·运维·macos