Linux多线程(十二)之【生产者消费者模型】

文章目录

生产者消费者模型

consumer/productor

321原则(便于记忆)

为何要使用生产者消费者模型

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

生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,

所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,

消费者不找生产者要数据,而是直接从阻塞队列里取,

阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。

这个阻塞队列就是用来给生产者和消费者解耦的。

生产者消费者模型优点
  1. 解耦
  2. 支持并发
  3. 支持忙闲不均

生产消费模型的高效问题

基于BlockingQueue的生产者消费者模型

BlockingQueue

在多线程编程中阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构。

其与普通的队列区别在于,

当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放入了元素;

当队列满时,往队列里存放元素的操作也会被阻塞,直到有元素被从队列中取出

(以上的操作都是基于不同的线程来说的,线程在对阻塞队列进程操作时会被阻塞)

C++ queue模拟阻塞队列的生产消费模型

代码:

单线程生产消费模型

blockqueue.hpp

c++ 复制代码
#pragma once

#include <iostream>
#include <pthread.h>
#include <queue>

using namespace std;

template <class T>
class blockqueue
{
    static const int defaultnum = 20;

public:
    blockqueue(int maxcap = defaultnum)
        : maxcap_(maxcap)
    {
        pthread_mutex_init(&mutex_, nullptr);
        pthread_cond_init(&c_cond_, nullptr);
        pthread_cond_init(&p_cond_, nullptr);
        low_water=maxcap_/3;
        high_water=maxcap_*2/3;
    }
    ~blockqueue()
    {
        pthread_mutex_destroy(&mutex_);
        pthread_cond_destroy(&c_cond_);
        pthread_cond_destroy(&p_cond_);
    }
    T pop()
    {
        pthread_mutex_lock(&mutex_);
        if(q_.size()==0)
        {
            pthread_cond_wait(&c_cond_,&mutex_);//1.调用的时候,自动释放锁
        }
        T t=q_.front();             // 你想消费,就直接能消费吗?不一定。你得先确保消费条件满足
        q_.pop();
        if(q_.size()<low_water)pthread_cond_signal(&p_cond_);
        pthread_mutex_unlock(&mutex_);
        return t;
    }
    void push(const T &in)
    {
        pthread_mutex_lock(&mutex_);
        if(q_.size()==maxcap_)
        {
            pthread_cond_wait(&p_cond_,&mutex_);//1.调用的时候,自动释放锁
        }
        q_.push(in);                // 你想生产,就直接能生产吗?不一定。你得先确保生产条件满足
        if(q_.size()>high_water)pthread_cond_signal(&c_cond_);
        pthread_mutex_unlock(&mutex_);

    }

private:
    queue<T> q_;
    int maxcap_;
    // int mincap_;
    pthread_mutex_t mutex_;
    pthread_cond_t c_cond_;
    pthread_cond_t p_cond_;
    int low_water;
    int high_water;
};

main.cc

c++ 复制代码
#include<iostream>
#include"blockqueue.hpp"
#include<unistd.h>

using namespace std;

void*Consumer(void*args)
{
    blockqueue<int> *bq=static_cast<blockqueue<int> *>(args);

    while(1)
    {
        int data=bq->pop();
        cout<<"消费了一个数据: "<<data<<endl;
        // sleep(1);
    }
}

void*Productor(void*args)
{
    blockqueue<int> *bq=static_cast<blockqueue<int> *>(args);
    int data=0;
    while(1)
    {
        data++;
        bq->push(data);
        cout<<"生产了一个数据: "<<data<<endl;
        sleep(1);
    }
}

int main()
{
    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);
    delete bq;

    return 0;
}

消费者sleep

两者都不sleep

多线程生产消费模型

补充:

  1. 为什么判断条件要放到加锁之后?

因为判断临界资源条件是否满足,也是在访问临界资源!

判断临界资源是否就绪,是通过在临界区内部判断的!

  1. 如果临界资源未就绪,那么线程就要进行等待。

等待的时候,线程是持有锁的!所以调用wait时,自动释放锁。

如果不释放锁,直接等待,那么等待的线程就没有线程可以唤醒了,

因为其他线程都在锁外,进不去临界区。

该线程因为唤醒而返回的时候,重新持有锁了。

  1. 如果线程在wait时,被误唤醒了呢?

伪唤醒的概念

解决方法:判断条件时用while,不用if

blockqueue.hpp

c++ 复制代码
#pragma once

#include <iostream>
#include <pthread.h>
#include <queue>

using namespace std;

template <class T>
class blockqueue
{
    static const int defaultnum = 20;

public:
    blockqueue(int maxcap = defaultnum)
        : maxcap_(maxcap)
    {
        pthread_mutex_init(&mutex_, nullptr);
        pthread_cond_init(&c_cond_, nullptr);
        pthread_cond_init(&p_cond_, nullptr);
        // low_water=maxcap_/3;
        // high_water=maxcap_*2/3;
    }
    ~blockqueue()
    {
        pthread_mutex_destroy(&mutex_);
        pthread_cond_destroy(&c_cond_);
        pthread_cond_destroy(&p_cond_);
    }
    T pop()
    {
        pthread_mutex_lock(&mutex_);
        while(q_.size()==0)//做到防止代码被伪唤醒!
        {
            pthread_cond_wait(&c_cond_,&mutex_);//1.调用的时候,自动释放锁
        }
        T t=q_.front();             // 你想消费,就直接能消费吗?不一定。你得先确保消费条件满足
        q_.pop();
        // if(q_.size()<low_water)pthread_cond_signal(&p_cond_);
        pthread_cond_signal(&p_cond_);//pthread_cond_broadcast
        pthread_mutex_unlock(&mutex_);
        return t;
    }
    void push(const T &in)
    {
        pthread_mutex_lock(&mutex_);
        while(q_.size()==maxcap_)//做到防止代码被伪唤醒!
        {
            pthread_cond_wait(&p_cond_,&mutex_);//1.调用的时候,自动释放锁
        }
        q_.push(in);                // 你想生产,就直接能生产吗?不一定。你得先确保生产条件满足
        // if(q_.size()>high_water)pthread_cond_signal(&c_cond_);
        pthread_cond_signal(&c_cond_);
        pthread_mutex_unlock(&mutex_);

    }

private:
    queue<T> q_;
    int maxcap_;
    // int mincap_;
    pthread_mutex_t mutex_;
    pthread_cond_t c_cond_;
    pthread_cond_t p_cond_;
    // int low_water;
    // int high_water;
};

Task.hpp

c++ 复制代码
#pragma once
#include <iostream>
#include<string>
using namespace std;

enum
{
    Div_zero = 1,
    Mod_zero,
    Unknown
};

class Task
{
public:
    Task(int x, int y, char op)
        : a(x), b(y), op_(op), ret(0), exitcode(0)
    {
    }
    void Run()
    {
        switch (op_)
        {
        case '+':
            ret = a + b;
            break;
        case '-':
            ret = a - b;
            break;
        case '*':
            ret = a * b;
            break;
        case '/':
        {
            if (b == 0)
                exitcode = Div_zero;
            else
                ret = a / b;
        }
        break;
        case '%':
        {
            if (b == 0)
                exitcode = Mod_zero;
            else
                ret = a % b;
        }
        break;
        default:
            exitcode=Unknown;
            break;
        }
    }
    string GetTask()
    {
        string r=to_string(a);
        r+=op_;
        r+=to_string(b);
        r+="=???";
        return r;
    }
    string Getret()
    {
        string r=to_string(a);
        r+=op_;
        r+=to_string(b);
        r+="=";
        r+=to_string(ret);
        r+=" [ exitcode: ";
        r+=to_string(exitcode);
        r+=" ]";
        return r;
    }
    void operator()()
    {
        Run();
    }
    ~Task() {}

private:
    int a;
    int b;
    char op_;

    int ret;
    int exitcode;
};

main.cc

c++ 复制代码
#include <iostream>
#include "blockqueue.hpp"
#include <unistd.h>
#include "Task.hpp"
#include <ctime>

using namespace std;

string oper = "+-*/%";

void *Consumer(void *args)
{
    blockqueue<Task> *bq = static_cast<blockqueue<Task> *>(args);

    while (1)
    {
        Task data = bq->pop();
        // data.Run();
        // 计算
        // data.Run();
        data();
        cout << "处理了一个任务 , 运算结果为: " << data.Getret() << " ,thread id: " << pthread_self() << endl;
        // sleep(1);
    }
}

void *Productor(void *args)
{
    int len = oper.size();
    blockqueue<Task> *bq = static_cast<blockqueue<Task> *>(args);
    // int x=0,y=0;
    // Task data(x,y);
    while (1)
    {
        // 模拟生产者生产数据
        int x = rand() % 10 + 1; //[1,10]
        int y = rand() % 10;     //[0,9];
        char op = oper[rand() % len];
        Task data(x, y, op);
        usleep(10);

        // 计算
        bq->push(data);
        cout << "生产了一个任务: " << data.GetTask() << " ,thread id: " << pthread_self() << endl;
        sleep(1);
    }
}

int main()
{
    srand(time(nullptr));
    blockqueue<Task> *bq = new blockqueue<Task>();
    pthread_t c[3], p[5];
    for (int i = 0; i < 3; i++)
    {
        pthread_create(c + i, nullptr, Consumer, bq);
    }
    for (int i = 0; i < 5; i++)
    {
        pthread_create(p + i, nullptr, Productor, bq);
    }
    
    for (int i = 0; i < 3; i++)
    {
        pthread_join(c[i], nullptr);
    }
    for (int i = 0; i < 5; i++)
    {
        pthread_join(p[i], nullptr);
    }
    delete bq;

    return 0;
}
相关推荐
pipip.40 分钟前
UDP————套接字socket
linux·网络·c++·网络协议·udp
zkmall43 分钟前
企业电商解决方案哪家好?ZKmall模块商城全渠道支持 + 定制化服务更省心
大数据·运维·重构·架构·开源
云资源服务商3 小时前
解锁阿里云日志服务SLS:云时代的日志管理利器
服务器·阿里云·云计算
绝不偷吃3 小时前
ELK日志分析系统
运维·elk·jenkins
朱包林4 小时前
day45-nginx复杂跳转与https
linux·运维·服务器·网络·云计算
孙克旭_4 小时前
day045-nginx跳转功能补充与https
linux·运维·nginx·https
孞㐑¥5 小时前
Linux之Socket 编程 UDP
linux·服务器·c++·经验分享·笔记·网络协议·udp
Hacker_Oldv6 小时前
软件测试(功能、工具、接口、性能、自动化、测开)详解
运维·自动化
Java樱木6 小时前
使用字节Trae + MCP,UI 到网页自动化。
运维·自动化
柳鲲鹏6 小时前
WINDOWS最快布署WEB服务器:apache2
服务器·前端·windows