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;
}
相关推荐
A小辣椒1 天前
TShark:Wireshark CLI 功能
linux
A小辣椒1 天前
TShark:基础知识
linux
AlfredZhao1 天前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao2 天前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334662 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪2 天前
linux 拷贝文件或目录到指定的位置
linux
大树883 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠3 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质3 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
bush43 天前
嵌入式linux学习记录十四、术语
linux·嵌入式