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:基于环形队列的生产者消费者模型

相关推荐
watermelonoops22 分钟前
Deepin和Windows传文件(Xftp,WinSCP)
linux·ssh·deepin·winscp·xftp
疯狂飙车的蜗牛1 小时前
从零玩转CanMV-K230(4)-小核Linux驱动开发参考
linux·运维·驱动开发
远游客07134 小时前
centos stream 8下载安装遇到的坑
linux·服务器·centos
马甲是掉不了一点的<.<4 小时前
本地电脑使用命令行上传文件至远程服务器
linux·scp·cmd·远程文件上传
jingyu飞鸟4 小时前
centos-stream9系统安装docker
linux·docker·centos
超爱吃士力架4 小时前
邀请逻辑
java·linux·后端
cominglately6 小时前
centos单机部署seata
linux·运维·centos
魏 无羡7 小时前
linux CentOS系统上卸载docker
linux·kubernetes·centos
CircleMouse7 小时前
Centos7, 使用yum工具,出现 Could not resolve host: mirrorlist.centos.org
linux·运维·服务器·centos
木子Linux7 小时前
【Linux打怪升级记 | 问题01】安装Linux系统忘记设置时区怎么办?3个方法教你回到东八区
linux·运维·服务器·centos·云计算