【Linux】生产者消费者模型——阻塞队列BlockQueue

一、生产者消费者模型

生产消费理解

引入:举个例子,比如我们学生想买东西,但是如果没有交易场所超市,那么我们只能去供货商去买东西,那我们只能如果要一件供货商只能生成一件,对于供货商来说生成的成本太大了,所以有了交易场所超市这个媒介的存在。目的就是为了集中需求,分发产品。

image-20230420084005988

消费者与生产者之间通过了超市进行交易。当生产者不需要的时候,供货商还可以继续声场,当供货商不再生产的时候消费者还能买得到!这样生产和消费就能进行解耦了。而我们把临时的宝成产品的场所称为缓冲区。

生产的过程和消费的过程——解耦

临时的保存产品的场所——缓冲区

当main函数调用函数func的时候,main函数会生产数据交给函数func,函数func会把数据暂时保存,而函数func会消费数据,这其实就是生产消费模型。但是当我们调用函数func的时候,main函数什么都不做,在那里阻塞等待函数返回,我们把main函数和调用函数func之间称为强耦合。

生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。

生产消费关系

image-20230420085851537

生产和消费都要看到“超市”,所以“超市”是一块共享资源。而既然是共享资源就会涉及到多线程访问,那么这块共享资源就要被保护起来

三种关系:生产者和生产者(互斥),消费者和消费者(互斥),生产者和消费者(互斥&&同步),互斥保证共享资源的安全性,,同步是为了提高访问效率

二种角色:生产者线程,消费者线程

一个交易场所:一段特定结构的缓冲区

想写生产消费模型,本质就是在维护321原则

挖掘特点

1.未来生产线程和消费线程进行解耦

2.支持生产和消费的一段时间的忙闲不均的问题(缓存区有数据有空间)

3.生产者专注生产,消费专注消费,提高效率

如果超市缓冲区满了,生产者只能进行等待,如果超市缓冲区为空,消费者只能进行等待。


二、基于blockqueue的生产和消费模型

阻塞队列:阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构

阻塞队列为空时,从阻塞队列中获取元素的线程将被阻塞,直到阻塞队列被放入元素。
阻塞队列已满时,往阻塞队列放入元素的线程将被阻塞,直到有元素被取出。

image-20230420093240803

单生产单消费计算

随机数

下面以单生产单消费为例子:

//BlockQueue.hpp
#pragma once
#include <iostream>
#include <queue>
#include <pthread.h>
const int gmaxcap =5;
template <class T>
class BlockQueue
{
public:
    BlockQueue(const int&maxcap = gmaxcap):_maxcap(maxcap)
    {
        pthread_mutex_init(&_mutex,nullptr);
        pthread_cond_init(&_pcond,nullptr);
        pthread_cond_init(&_ccond,nullptr);
    }
    void push(const T& in)
    {
        pthread_mutex_lock(&_mutex);
        while(is_full())
        {
            pthread_cond_wait(&_pcond,&_mutex);//因为生产条件不满足无法生产,此时我们的生产者进行等待
        }
        _q.push(in);
        //pthread_cond_signal:这个函数可以放在临界区内部,也可以放在外部
        pthread_cond_signal(&_ccond);
        pthread_mutex_unlock(&_mutex);
        //pthread_cond_signal(&_ccond);
    }
    void pop(T*out)//输出型参数,*,输入输出型:&
    {
        pthread_mutex_lock(&_mutex);
        while(is_empty())
        {
            pthread_cond_wait(&_ccond,&_mutex);
        }
        *out = _q.front();
        _q.pop();
        pthread_cond_signal(&_pcond);
        pthread_mutex_unlock(&_mutex);
    }
    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_pcond);
        pthread_cond_destroy(&_ccond);
    }
private:
    bool is_empty(){return _q.empty();}
    bool is_full(){return _q.size()==_maxcap;}
private:
    std::queue<T> _q;
    int _maxcap;//队列上限
    pthread_mutex_t _mutex;
    pthread_cond_t _pcond;//生产者条件变量
    pthread_cond_t _ccond;//消费者条件变量
};

//mainCp.cc
void* consumer(void * bq_)
{
    BlockQueue<int>* bq = static_cast<BlockQueue<int>*>(bq_);
    while(true)
    {
        int data;
        bq->pop(&data);
        std::cout<<"消费数据: "<<data<<std::endl;
        sleep(1);
    }
    return nullptr;
}
void*productor(void*bq_)
{
    BlockQueue<int>*bq = static_cast<BlockQueue<int>*>(bq_);
    while(true)
    {
        int data = rand()%10+1;
        bq->push(data);
        std::cout<<"生产数据: "<<data<<std::endl;
    }
    return nullptr;
}
int main()
{
    srand((unsigned long)time(nullptr)^getpid());
    BlockQueue<int> *bq = new  BlockQueue<int>();
    pthread_t c,p;
    pthread_create(&c,nullptr,consumer,bq);
    pthread_create(&p,nullptr,productor,bq);

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

pthread_cond_wait函数的第二个参数必须是我们正在使用的互斥锁,满了就会进行等待,如果像之前一样把锁拿走,那么其他线程就无法访问共享资源
a.pthread_cond_wait:该函数调用的时候,会以原子性的方式,将锁释放,并将自己挂起
b.pthread_cond_wait:该函数在被唤醒返回的时候,会自动的重新获取你传入的锁

pthread_cond_signal伪唤醒:判断的问题:假设生产者有10个,消费者只有一个,消费一下数据,如果是pthread_cond_broadcast是把10个线程同时唤醒,可是只需要生产一个数据,而同时把10个线程唤醒而如果是if判断的时候push就会出问题了

image-20230420105755836

如果生产者生产慢,消费者消费快:生产一个消费一个,而且消费的都是最新的数据

image-20230420112933795

如果生产者生产快,消费者消费慢:稳定后,消费一个生产一个

image-20230420113649157

计算器任务Task

Task.hpp:包含func_t的回调函数,这个函数就是进行数据计算的回调函数

#pragma once
#include <iostream>
#include <functional>
#include <cstdio>
class Task
{
    using func_t = std::function<int(int,int,char)>;
public:
    Task()
    {}
    Task(int x,int y,char op,func_t func)
    :_x(x),_y(y),_op(op),_callback(func)
    {}
    std::string operator()()
    {
        int result = _callback(_x,_y,_op);
        char buffer[1024];
        snprintf(buffer,sizeof buffer,"%d %c %d = %d",_x,_op,_y,result);
        return buffer;
    }
    std::string toTaskString()
    {
        char buffer[1024];
        snprintf(buffer,sizeof buffer,"%d %c %d = ?",_x,_op,_y);
        return buffer;
    }
private:
    int _x;
    int _y;
    char _op;
    func_t _callback;
};

BlockQueue.hpp

#pragma once
#include <iostream>
#include <queue>
#include <pthread.h>
const int gmaxcap =500;
template <class T>
class BlockQueue
{
public:
    BlockQueue(const int&maxcap = gmaxcap):_maxcap(maxcap)
    {
        pthread_mutex_init(&_mutex,nullptr);
        pthread_cond_init(&_pcond,nullptr);
        pthread_cond_init(&_ccond,nullptr);
    }

    void push(const T& in)
    {
        pthread_mutex_lock(&_mutex);
        while(is_full())
        {
            pthread_cond_wait(&_pcond,&_mutex);
        }
        _q.push(in);
        pthread_cond_signal(&_ccond);
        pthread_mutex_unlock(&_mutex);
    }

    void pop(T*out)
    {
        pthread_mutex_lock(&_mutex);
        if(is_empty())
        {
            pthread_cond_wait(&_ccond,&_mutex);
        }
        *out = _q.front();
        _q.pop();
        pthread_cond_signal(&_pcond);
        pthread_mutex_unlock(&_mutex);
    }

    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_pcond);
        pthread_cond_destroy(&_ccond);
    }
private:
    bool is_empty(){return _q.empty();}
    bool is_full(){return _q.size()==_maxcap;}
private:
    std::queue<T> _q;
    int _maxcap;
    pthread_mutex_t _mutex;
    pthread_cond_t _pcond;
    pthread_cond_t _ccond;
};

Main.cc

#include <ctime>
#include <sys/types.h>
#include <unistd.h>
#include "BlockQueue.hpp"
#include "Task.hpp"
const std::string oper = "+-*/%"; 
int mymath(int x,int y,char op)
{
    int result = 0;
    switch (op)
    {
    case '+':
        result = x + y;
        break;
    case '-':
        result = x - y;
        break;
    case '*':
        result = x * y;
        break;
    case '/':
    {
        if (y == 0)
        {
            std::cerr << "div zero error!" << std::endl;
            result = -1;
        }
        else
            result = x / y;
    }
        break;
    case '%':
    {
        if (y == 0)
        {
            std::cerr << "mod zero error!" << std::endl;
            result = -1;
        }
        else
            result = x % y;
    }
        break;
    default:
        break;
    }
    return result;
}
void* consumer(void * bq_)
{
    BlockQueue<Task>* bq = static_cast<BlockQueue<Task>*>(bq_);
    while(true)
    {
        Task t;
        bq->pop(&t);
        std::cout<<"消费任务: "<<t()<<std::endl;
    }
    return nullptr;
}
void*productor(void*bq_)
{
    BlockQueue<Task>*bq = static_cast<BlockQueue<Task>*>(bq_);
    while(true)
    {
        int x = rand()%100+1;
        int y = rand()%10;
        int operCode = rand()%oper.size();
        Task t(x,y,oper[operCode],mymath);
        bq->push(t);
        std::cout<<"生产任务: "<<t.toTaskString()<<std::endl;  
        sleep(1);
    }
    return nullptr;
}
int main()
{
    srand((unsigned long)time(nullptr)^getpid());
    BlockQueue<Task> *bq = new  BlockQueue<Task>();
    pthread_t c,p;
    pthread_create(&c,nullptr,consumer,bq);
    pthread_create(&p,nullptr,productor,bq);

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

image-20230420121405826

存储任务

定义结构体BlockQueues封装计算任务的阻塞队列和存储任务的阻塞队列,创建生产者线程,消费者线程,存储线程执行各自的方法

image-20230420201304498

blockqueue.hpp

#pragma once
#include <iostream>
#include <queue>
#include <pthread.h>
const int gmaxcap = 500;
template <class T>
class BlockQueue
{   
public:
    BlockQueue(const int &maxcap = gmaxcap):_maxcap(maxcap)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_pcond, nullptr);
        pthread_cond_init(&_ccond, nullptr);
    }
    void push(const T &in)
    {
        pthread_mutex_lock(&_mutex);
        while(is_full())
        {
            pthread_cond_wait(&_pcond, &_mutex); 
        }
        _q.push(in);
        pthread_cond_signal(&_ccond);
        pthread_mutex_unlock(&_mutex);
    }
    void pop(T *out)
    {
        pthread_mutex_lock(&_mutex);
        while(is_empty())
        {
            pthread_cond_wait(&_ccond, &_mutex);
        }
        *out = _q.front();
        _q.pop();
        pthread_cond_signal(&_pcond); 
        pthread_mutex_unlock(&_mutex);
    }
    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_pcond);
        pthread_cond_destroy(&_ccond);
    }
private:
    bool is_empty()
    {
        return _q.empty();
    }
    bool is_full()
    {
        return _q.size() == _maxcap;
    }
private:
    std::queue<T> _q;
    int _maxcap;
    pthread_mutex_t _mutex;
    pthread_cond_t _pcond; 
    pthread_cond_t _ccond;
};

Task.hpp

#pragma once

#include <iostream>
#include <string>
#include <cstdio>
#include <functional>

class CalTask
{
    using func_t = std::function<int(int,int,char)>;
    // typedef std::function<int(int,int)> func_t;
public:
    CalTask()
    {}
    CalTask(int x, int y, char op, func_t func)
    :_x(x), _y(y), _op(op), _callback(func)
    {}
    std::string operator()()
    {
        int result = _callback(_x, _y, _op);
        char buffer[1024];
        snprintf(buffer, sizeof buffer, "%d %c %d = %d", _x, _op, _y, result);
        return buffer;
    }
    std::string toTaskString()
    {
        char buffer[1024];
        snprintf(buffer, sizeof buffer, "%d %c %d = ?", _x, _op, _y);
        return buffer;
    }
private:
    int _x;
    int _y;
    char _op;
    func_t _callback;
};

const std::string oper = "+-*/%";

int mymath(int x, int y, char op)
{
    int result = 0;
    switch (op)
    {
    case '+':
        result = x + y;
        break;
    case '-':
        result = x - y;
        break;
    case '*':
        result = x * y;
        break;
    case '/':
    {
        if (y == 0)
        {
            std::cerr << "div zero error!" << std::endl;
            result = -1;
        }
        else
            result = x / y;
    }
        break;
    case '%':
    {
        if (y == 0)
        {
            std::cerr << "mod zero error!" << std::endl;
            result = -1;
        }
        else
            result = x % y;
    }
        break;
    default:
        // do nothing
        break;
    }

    return result;
}

class SaveTask
{
    typedef std::function<void(const std::string&)> func_t;
public:
    SaveTask()
    {}
    SaveTask(const std::string &message, func_t func)
    : _message(message), _func(func)
    {}
    void operator()()
    {
        _func(_message);
    }
private:
    std::string _message;
    func_t _func;
};

void Save(const std::string &message)
{
    const std::string target = "./log.txt";
    FILE *fp = fopen(target.c_str(), "a+");
    if(!fp)
    {
        std::cerr << "fopen error" << std::endl;
        return;
    }
    fputs(message.c_str(), fp);
    fputs("\n", fp);
    fclose(fp);
}

MianCp.cc

#include <iostream>
#include <queue>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
#include <ctime>
#include "blockqueue.hpp"
#include "Task.hpp"
template<class C,class K>
struct BlockQueues
{
    BlockQueue<C> *c_bq;
    BlockQueue<K> *s_bq;
};
void* productor(void*args)
{
    BlockQueue<CalTask>* bq = (static_cast<BlockQueues<CalTask,SaveTask>*>(args))->c_bq;
    while(true)
    {
        int x = rand()%10+1;
        int y = rand()%5+1;
        int operCode = rand()%oper.size();
        CalTask t(x,y,oper[operCode],mymath);
        bq->push(t);
        std::cout<<"productor thread,生产计算任务: "<<t.toTaskString()<<std::endl;
        sleep(1);
    }
    return nullptr;
}
void* consumer(void*args)
{
    BlockQueue<CalTask>* bq = (static_cast<BlockQueues<CalTask,SaveTask>*>(args))->c_bq;
    BlockQueue<SaveTask>* save_bq = (static_cast<BlockQueues<CalTask,SaveTask>*>(args))->s_bq;
    while(true)
    {
        CalTask t;
        bq->pop(&t);
        std::string result = t();
        std::cout<<"cal thread,完成计算任务: "<<result<<" ... done"<<std::endl;

        SaveTask save(result,Save);
        save_bq->push(save);
        std::cout<<"cal thread,推送存储任务完成..."<<std::endl;
    }
    return nullptr;
}
void*saver(void*args)
{
    BlockQueue<SaveTask>* save_bq = (static_cast<BlockQueues<CalTask,SaveTask>*>(args))->s_bq;
    while(true)
    {
        SaveTask t;
        save_bq->pop(&t);
        t();
        std::cout<<"save thread,保存任务完成..."<<std::endl;
    }
    return nullptr;
}
int main()
{
    srand((unsigned int)time(nullptr)^getpid());
    BlockQueues<CalTask,SaveTask> bqs;
    bqs.c_bq = new BlockQueue<CalTask>();
    bqs.s_bq = new BlockQueue<SaveTask>();
    pthread_t c,p,s;
    pthread_create(&p,nullptr,productor,&bqs);
    pthread_create(&c,nullptr,consumer,&bqs);
    pthread_create(&s,nullptr,saver,&bqs);
    pthread_join(c,nullptr);
    pthread_join(p,nullptr);
    pthread_join(s,nullptr);
    delete bqs.c_bq;
    delete bqs.s_bq;
    return 0;
}

image-20230420181958543

多生产多消费

只需要稍微改一改MainCp.cc即可完成多生产多消费,其他文件代码不需要更改

MainCp.cc

#include <ctime>
#include <sys/types.h>
#include <unistd.h>
#include "blockqueue.hpp"
#include "Task.hpp"
template<class C,class S>
class BlockQueues
{
public:
    BlockQueue<C>*c_bq;
    BlockQueue<S>*s_bq;
}; 
void*productor(void*bqs_)
{
    BlockQueue<CalTask>* bq = (static_cast<BlockQueues<CalTask,SaveTask>*>(bqs_))->c_bq;
    while(true)
    {
        int x = rand()%100+1;
        int y = rand()%10;
        int operCode = rand()%oper.size();
        CalTask t(x,y,oper[operCode],mymath);
        bq->push(t);
        std::cout<<"productor thread,生产计算任务: "<<t.toTaskString()<<std::endl;
        sleep(1);
    }
    return nullptr;
}
void* consumer(void * bqs_)
{
    BlockQueue<CalTask>* bq = (static_cast<BlockQueues<CalTask,SaveTask>*>(bqs_))->c_bq;
    BlockQueue<SaveTask>* save_bq = (static_cast<BlockQueues<CalTask,SaveTask>*>(bqs_))->s_bq;

    while(true)
    {
        CalTask t;
        bq->pop(&t);
        std::string result = t();
        std::cout<<"cal thread,完成计算任务: "<<result<<" ... done"<<std::endl;

        // SaveTask save(result,Save);
        // save_bq->push(save);
        // std::cout<<"cal thread,推送存储任务完成..."<<std::endl;
        // sleep(1);
    }
    return nullptr;
}
void* saver(void* bqs_)
{
    BlockQueue<SaveTask>* save_bq = (static_cast<BlockQueues<CalTask,SaveTask>*>(bqs_))->s_bq;
    while(true)
    {
        SaveTask t;
        save_bq->pop(&t);
        t();
        std::cout<<"save thread,保存任务完成..."<<std::endl;
    }
    return nullptr;
};
int main()
{
    srand((unsigned long)time(nullptr)^getpid());
    BlockQueues<CalTask,SaveTask> bqs;
    bqs.c_bq = new  BlockQueue<CalTask>();
    bqs.s_bq = new  BlockQueue<SaveTask>();
    pthread_t c[2],p[3];
    pthread_create(p,nullptr,productor,&bqs);
    pthread_create(p+1,nullptr,productor,&bqs);
    pthread_create(p+2,nullptr,productor,&bqs);
    pthread_create(c,nullptr,consumer,&bqs);
    pthread_create(c+1,nullptr,consumer,&bqs);
  
    pthread_join(c[0],nullptr);
    pthread_join(c[1],nullptr);
    pthread_join(p[0],nullptr);
    pthread_join(p[1],nullptr); 
    pthread_join(p[2],nullptr);

    delete bqs.c_bq;
    delete bqs.s_bq;
    return 0;
}

三、总结

多生产者多消费是可以的,这个阻塞队列在进程进入前要加锁,竞争这把锁。消费者与消费者也要竞争锁

**换句话来说:在阻塞队列中,无论外部线程再多,真正进入到阻塞队列里生产或消费的线程永远只有一个。**在一个任务队列中,有多个生产者与多个消费者,由于有锁的存在,所以任意时刻只有一个执行流在锁里面放。

生产消费模型高效在哪里:

对于生产者而言,要向blockQueue里面放置任务,对于消费者而言,要从blockQueue里面拿去任务

对于生产者,任务从哪里来?获取任务和构建任务是要花时间的

对于消费者,难道它把任务从任务队列中拿出来就完了吗?消费者拿到任务之后,后续还有没有任务?

高效体现在一个线程拿出来任务可能正在做计算,它在做计算的同时,其他线程可以继续从队列中拿,继续做运算,高效并不是体现在从队列中拿数据高效!而是我们可以让一个、多个线程并发的同时计算多个任务!在计算多个任务的同时,并不影响其他线程,继续从队列里拿任务的过程。也就是说,生产者消费者模型的高效:可以在生产之前与消费之后让线程并行执行,不要认为生产者消费模式仅仅只是把任务生产到队列的过程就是生产过程,生产过程:1.拿任务、需要费点劲2.拿到后再放到队列里面整个一体,整个生产的过程;整个消费的过程:不是把任务拿到线程的上下文中就完了,拿到之后还要进行计算或存储这些工作才是消费的过程在生产前和和消费后我们多个线程是可以并发的。

猜你喜欢

转载自blog.csdn.net/weixin_60478154/article/details/130274809