_Linux多线程--生产者消费者模型篇

news2024/11/17 5:42:57

文章目录

  • 1. 为何要使用生产者消费者模型
  • 2. 基于BlockingQueue的生产者消费者模型
  • 3. C++ queue模拟阻塞队列的生产消费模型
    • 条件变量使用规范
    • 简单测试
      • 1. BlockQueue (缓存--超市)
      • 2. ConProd.cc
      • 3. 结果展示
    • 升级版测试&&设计与RAII风格的加锁方式
      • 1. BlockQueue.hpp
      • 2. Task.hpp
      • 3. LockGuard.hpp(RAII风格的加锁方式)
      • 4. ConProd.cc
      • 5. 结果展示:

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

生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。

  • 例子:
    就好比一个正在运行的网络程序;我们拿数据需要时间,我们处理数据也需要时间;那么当没数据时,另一个处理数据线程不就一直等吗?这就有点浪费效率了。那么中间有个缓存,就可以大大提高效率了。

生产者消费者模型优点

  • 解耦
  • 支持并发
  • 支持忙闲不均

在这里插入图片描述

  • 三种关系:
    • 生产者和生成者(互斥关系、竞争)
    • 消费者和消费者(互斥关系、竞争)
    • 生产者和消费者(互斥、同步竞争)
  • 二种角色: 生产者/消费者
  • 一个交易场所:超市

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

BlockingQueue

在多线程编程中阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构。其与普通的队列区别在于,当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放入了元素;当队列满时,往队列里存放元素的操作也会被阻塞,直到有元素被从队列中取出(以上的操作都是基于不同的线程来说的,线程在对阻塞队列进程操作时会被阻塞)

在这里插入图片描述
—(图片摘于相关教材资料)

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

条件变量使用规范

  • 等待条件代码
pthread_mutex_lock(&mutex);
while (条件为假)
pthread_cond_wait(cond, mutex);
修改条件
pthread_mutex_unlock(&mutex);
  • 给条件发送信号代码
pthread_mutex_lock(&mutex);
设置条件为真
pthread_cond_signal(cond);
pthread_mutex_unlock(&mutex);
  • 例子:
    在这里插入图片描述
  • 注意:
  • pthread_cond_wait第二个参数是一个锁,当成功调用wait之后,传入的锁,会被自动释放!
  • 当我被唤醒时,我从哪里醒来呢??
    • 从哪里阻塞挂起,就从哪里唤醒, 被唤醒的时候,我们还是在临界区被唤醒的
    • 当我们被唤醒的时候,pthread_cond_wait,会自动帮助我们线程获取锁
  • pthread_cond_wait: 但是只要是一个函数,就可能调用失败 && pthread_cond_wait: 可能存在 伪唤醒 的情况
    • 不用if判断;用while在这里插入图片描述

简单测试

1. BlockQueue (缓存–超市)

#pragma once

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

#define gDefaultCap 5
template <class T>
class BlockQueue
{
    bool isQueueEmpty()
    {
        return _bq.size() == 0;
    }
    bool isQueueFull()
    {
        return _bq.size() == _capacity;
    }

public:
    BlockQueue(int capacity = gDefaultCap) : _capacity(capacity)
    {
        pthread_mutex_init(&_mtx, nullptr);
        pthread_cond_init(&_Empty, nullptr);
        pthread_cond_init(&_Full, nullptr);
    }
    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mtx);
        pthread_cond_destroy(&_Empty);
        pthread_cond_destroy(&_Full);
    }
    void push(const T &in) // 生产者
    {
        pthread_mutex_lock(&_mtx);
        while (isQueueFull())
            pthread_cond_wait(&_Full, &_mtx); // 检测临界资源条件
        // 数据满了; 等待消费者消费数据
        _bq.push(in); // 生产者放数据
        //if(_bq.size() >= _capacity/2) pthread_cond_signal(&_Empty); //数据积累到>=一半时再发送
        pthread_cond_signal(&_Empty); // 唤醒; 通知消费者,可以消费了
        pthread_mutex_unlock(&_mtx);
    }
    void pop(T *out) // 消费者
    {
        pthread_mutex_lock(&_mtx);
        while (isQueueEmpty())
            pthread_cond_wait(&_Empty, &_mtx); // 检测临界资源条件
        // 数据为空; 等待生产者生产数据
        *out = _bq.front();
        _bq.pop();           // 消费者消费
        pthread_cond_signal(&_Full); // 唤醒; 通知生产者,可以生成了了
        pthread_mutex_unlock(&_mtx);
    }

public:
    std::queue<T> _bq;     // 阻塞队列
    int _capacity;         // 容量上限
    pthread_mutex_t _mtx;  // 通过互斥锁保证队列安全
    pthread_cond_t _Empty; // 用它来表示bq 是否空的条件
    pthread_cond_t _Full;  //  用它来表示bq 是否满的条件
};

2. ConProd.cc

#include <iostream>
#include <pthread.h>
#include <ctime>
#include <unistd.h>

#include "BlockQueue.hpp"

void *productor(void *args)
{
    BlockQueue<int> *bq = (BlockQueue<int> *)args;
    while (true)
    {
        int a = rand() % 10 + 1;
        bq->push(a);
        std::cout << "productor生成的数据是" << a << std::endl;
    }
    return nullptr;
}

void *consumer(void *args)
{
    BlockQueue<int> *bq = (BlockQueue<int> *)args;
    while (true)
    {
        usleep(rand()%1000);
        int a;
        bq->pop(&a);
        std::cout << "consumer消费的数据是" << a << std::endl;
        sleep(1);
    }
    return nullptr;
}

int main()
{
    // 随机数种子
    srand((uint64_t)time(nullptr) ^ getpid() ^ 0x202300);
    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;
}

3. 结果展示

在这里插入图片描述

升级版测试&&设计与RAII风格的加锁方式

1. BlockQueue.hpp

#pragma once

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

#include "LockGuard.hpp"
#define gDefaultCap 5
template <class T>
class BlockQueue
{
    bool isQueueEmpty()
    {
        return _bq.size() == 0;
    }
    bool isQueueFull()
    {
        return _bq.size() == _capacity;
    }

public:
    BlockQueue(int capacity = gDefaultCap) : _capacity(capacity)
    {
        pthread_mutex_init(&_mtx, nullptr);
        pthread_cond_init(&_Empty, nullptr);
        pthread_cond_init(&_Full, nullptr);
    }
    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mtx);
        pthread_cond_destroy(&_Empty);
        pthread_cond_destroy(&_Full);
    }
    void push(const T &in) // 生产者
    {
        // pthread_mutex_lock(&_mtx);
        LockGuard Lockguard(_mtx);
        while (isQueueFull())   //访问临界资源,100%确定,资源是就绪的!
            pthread_cond_wait(&_Full, &_mtx); // 检测临界资源条件
        // 数据满了; 等待消费者消费数据
        _bq.push(in); // 生产者放数据
        //if(_bq.size() >= _capacity/2) pthread_cond_signal(&_Empty); //数据积累到>=一半时再发送
        pthread_cond_signal(&_Empty); // 唤醒; 通知消费者,可以消费了
        //pthread_mutex_unlock(&_mtx);
    }// 出了函数后自动调用lockgrard 析构函数
    void pop(T *out) // 消费者
    {
        // pthread_mutex_lock(&_mtx);
        LockGuard Lockguard(_mtx);
        while (isQueueEmpty())
            pthread_cond_wait(&_Empty, &_mtx); // 检测临界资源条件
        // 数据为空; 等待生产者生产数据
        *out = _bq.front();
        _bq.pop();           // 消费者消费
        pthread_cond_signal(&_Full); // 唤醒; 通知生产者,可以生成了了
        // pthread_mutex_unlock(&_mtx);
    }

public:
    std::queue<T> _bq;     // 阻塞队列
    int _capacity;         // 容量上限
    pthread_mutex_t _mtx;  // 通过互斥锁保证队列安全
    pthread_cond_t _Empty; // 用它来表示bq 是否空的条件
    pthread_cond_t _Full;  //  用它来表示bq 是否满的条件
};

2. Task.hpp

#pragma once

#include <iostream>
#include <functional>

using func_t = std::function<int(int, int)>;

class Task
{
public:
    Task() {}                                                   // 便于获取任务
    Task(int x, int y, func_t func) : _x(x), _y(y), _func(func) // 制作任务
    {}

    int operator()() // 仿函数
    {
        return _func(_x, _y); // 函数调用
    }

public:
    int _x;
    int _y;
    func_t _func;
};

3. LockGuard.hpp(RAII风格的加锁方式)

#pragma once

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

class Mutex
{
public:
    Mutex(pthread_mutex_t& mtx):_mtx(mtx)
    {}
    void lock()
    {
        std::cout << "要进行加锁" << std::endl;
        pthread_mutex_lock(&_mtx);
    }
    void unlock()
    {
        std::cout << "要进行解锁" << std::endl;
        pthread_mutex_unlock(&_mtx);
    }
    ~Mutex()
    {}
public:
    pthread_mutex_t _mtx;
};

// RAII风格的加锁方式
class LockGuard
{
public:
    LockGuard(pthread_mutex_t& mtx):_mx(mtx)
    {
        _mx.lock();
    }
    ~LockGuard()
    {
        _mx.unlock();
    }

private:
    Mutex _mx;    
};

4. ConProd.cc

#include <iostream>
#include <pthread.h>
#include <ctime>
#include <unistd.h>

#include "BlockQueue.hpp"
#include "Task.hpp"

int myMul(int x, int y)
{
    return x * y;
}

void *productor(void *args)
{
    // BlockQueue<int> *bq = (BlockQueue<int> *)args;
    BlockQueue<Task> *bq = (BlockQueue<Task> *)args;
    while (true)
    {
        // int a = rand() % 10 + 1;
        // bq->push(a);
        // std::cout << "productor生成的数据是" << a << std::endl;

        // 制作任务
        int x = rand() % 10 + 1;
        usleep(rand() % 1000);
        int y = rand() % 5 + 1;
        std::cout << pthread_self() << ":发布任务..." << x << 'x' << y << "=" << '?' << std::endl; 
        Task t(x, y, myMul);
        bq->push(t);
        sleep(1);  
    }
    return nullptr;
}

void *consumer(void *args)
{
    // BlockQueue<int> *bq = (BlockQueue<int> *)args;
    BlockQueue<Task> *bq = (BlockQueue<Task> *)args;
    while (true)
    {
        // usleep(rand() % 1000);
        // int a;
        // bq->pop(&a);
        // std::cout << "consumer消费的数据是" << a << std::endl;
        // sleep(1);

        // 获取任务
        Task t;
        bq->pop(&t);

        // 完成任务
        std::cout << pthread_self() << ":执行任务..." << t._x << 'x' << t._y << "=" << t() << std::endl;
    }
    return nullptr;
}

#define CONSUMER_NUM 6
#define PRODUCTOR_NUM 3
int main()
{
    // 随机数种子
    srand((uint64_t)time(nullptr) ^ getpid() ^ 0x202300);
    // BlockQueue<int> *bq = new BlockQueue<int>();
    BlockQueue<Task> *bq = new BlockQueue<Task>();

    // 创建线程
    pthread_t c[CONSUMER_NUM], p[PRODUCTOR_NUM];
    for(int i=0; i<CONSUMER_NUM; ++i)
    {
        pthread_create(c+i, nullptr, consumer, bq);
    }
    for(int i=0; i<PRODUCTOR_NUM; ++i)
    {
        pthread_create(p+i, nullptr, productor, bq);
    }

    // 等待线程
    for(int i=0; i<CONSUMER_NUM; ++i)
    {
        pthread_join(c[i], nullptr);
    }
    for(int i=0; i<PRODUCTOR_NUM; ++i)
    {
        pthread_join(p[i], nullptr);
    }

    // 释放资源
    delete bq;

    return 0;
}

5. 结果展示:

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/164353.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MATLAB 实现路由算法详细教程(完整代码+数据)

问题描述&#xff1a;鉴于我们小组成员都来自计通学院&#xff0c;我们对专业知识计算机网络内的路由器进行研究。我们知道在整个互联网中&#xff0c;有着很多个小的无法互相连通的小网络&#xff0c;早在上世纪六十年代&#xff0c;针对不同网络无法互联的问题&#xff0c;路…

IC芯片类元件创建

--摘自凡亿教育 一、VS1003音频芯片 首先&#xff0c;先创建新的元器件库 按箭头所指的即可。 然后&#xff0c;我们右击箭头所指的键&#xff0c;选择第六个矩形。 然后&#xff0c;依次放置管脚 点击箭头所指&#xff0c;既可放置管脚。 由于我们放置管脚的时候&#xff…

python的webdriver应用

本文总结如何使用python的webdriver插件&#xff0c;应用自动化测试以及爬虫抓取数据。工具选择谷歌版本下载&#xff1a;https://www.iplaysoft.com/tools/chrome/webdriver版本&#xff1a;http://npm.taobao.org/mirrors/chromedriver/ 或https://chromedriver.storage.goog…

顺序表学习指南,请查收~

作者&#xff1a;爱塔居的博客_CSDN博客-JavaSE,数据结构领域博主 专栏&#xff1a;数据结构 作者简介&#xff1a;大三学生&#xff0c;希望一起进步&#xff01; 文章目录 目录 文章目录 一、顺序表基本概念 二、练习 一、顺序表基本概念 &#x1f33a;顺序表是用一段物理地…

常用工具的常用操作

写在前面 记录可能用到的各种工具常见技巧。 1&#xff1a;sublime 1.1&#xff1a;操作多列 首先选中要操作的列所在的行&#xff1a; 然后点击selection&#xff0c;spit lines&#xff1a; 接下来移动左右键就可以操作了&#xff0c;删除或者批量添加内容&#xff1a; 1…

创客匠人助力机构招生获客转化

后疫情时代&#xff0c;各行各业部署线上化成为一门必修课。 创客匠人作为一家专注教育培训行业的知识付费技术服务商&#xff0c;为了更好的帮助教培机构、教育企业立足于内容传播需求&#xff0c;打通线上线下资源通道&#xff0c;将线下资源向"线上核心平台"靠拢…

无极低码:100套大屏可视化源码,包含多个行业

随着互联网的发展&#xff0c;各项技术的不断成熟&#xff0c;数据可视化在新的时代&#xff0c;人们对数据的呈现方式开始有了新的要求。科技感、美观、直观、动感等等都成为现代软件系统新的设计和思考方向&#xff0c;特别是硬件的发展和数据的发展&#xff0c;数据分析的需…

Vue3商店后台管理系统设计文稿篇(一)

记录使用vscode构建Vue3商店后台管理系统&#xff0c;这是第一篇&#xff0c;主要记录Vue3项目创建过程&#xff0c;以及数据的挂载 文章目录一、Vue3项目创建二、取消代码规范检查三、数据简单挂载正文内容&#xff1a; 一、Vue3项目创建 使用如下命令全局安装yarn npm i -g …

Java开发 - Mybatis框架初体验

前言 在前文中&#xff0c;我们已经学习了Spring框架&#xff0c;Spring MVC框架&#xff0c;相信大家对这些基础的内容已经熟练使用了&#xff0c;今天&#xff0c;我们继续来学习Mybatis框架。就目前而言&#xff0c;Mybatis框架依然是比较实用的框架&#xff0c;这篇博客&a…

SpringMVC知识点记录

SpringMVC知识点记录1. SpringMVC简介2. 入门案例3. RequestMapping注解4. SpringMVC获取请求参数5. 域对象共享数据6.SpringMVC的视图7. RESTful8. RESTful 案例9. SpringMVC处理ajax请求10. 文件上传和下载11. 拦截器12. 异常处理器13. 注解配置SpringMVC14. SpringMVC执行流…

hgame2023 week1 writeup

#WEEK1 RE 1、re-test_your_IDA ida打开可见flag&#xff1a; int __cdecl main(int argc, const char **argv, const char **envp) {char Str1[24]; // [rsp20h] [rbp-18h] BYREFsub_140001064("%10s");if ( !strcmp(Str1, "r3ver5e") )sub_140001010…

移动端 - 搜索组件(search-input篇)

我们先来看一下最终效果 这样的搜索组件在移动端是很常见的, 大部分需求都是: 1. 搜索框进行搜索关键字 2. 热门搜索 3. 搜索历史 4. 搜索结果(提供上拉加载效果) 上述的基本需求也是我们现在需要去实现的, 先来说一下大致的方向: 1. search 一般都是一个路由组件, 所以先…

20.Isaac教程--Python接口(Python API)

Isaac Python接口(Python API) ISAAC教程合集地址: https://blog.csdn.net/kunhe0512/category_12163211.html 虽然 Isaac SDK 的大部分部分都是用 C 编码的&#xff0c;但您可以选择使用 Python 构建您的应用程序。 本文档介绍了 Isaac SDK 的 Python API。 Python API 允许您…

Day859.高性能队列Disruptor -Java 并发编程实战

高性能队列Disruptor Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于高性能队列Disruptor的内容。 并发容器 中Java SDK 提供了 2 个有界队列&#xff1a; ArrayBlockingQueueLinkedBlockingQueue 它们都是基于 ReentrantLock 实现的&#xff0c;在高并发场景下&…

人工智能的过去与未来——萌芽

1943年—M-P模型 美国神经生理学家Warren McCulloch和数理逻辑学家Walter Pitts在合作的《A logical calculus of the ideas immanent in nervous activity》论文中对生物神经元进行建模&#xff0c;并提出了一种形式神经元模型&#xff0c;命名为McCulloch-Pitts模型。 生物…

65. Python __init__方法

65. __init__方法 文章目录65. __init__方法1. 知识回顾在类的方法中调用类的属性2. 知识回顾调用方法时传值3.体验__init__方法4. __init__的作用5. __init__方法的写法6. __init__方法调用类的属性7. 课堂实操1. 知识回顾在类的方法中调用类的属性 【目标任务】 创建一个类…

C++程序设计——类的六个成员函数

类的六个成员函数 空类中真的什么都没有吗&#xff1f; 事实上任何一个类&#xff0c;在我们不写的情况下&#xff0c;都会自动生成6个默认的成员函数。 1.构造函数 概念&#xff1a; 构造函数是一个特殊的成员函数&#xff0c;名字与类名相同&#xff0c;实例化对象时由编译器…

【基于机械臂触觉伺服的物体操控研究】几种轨迹规划的算法及代码实现

我的毕设题目定为《基于机械臂触觉伺服的物体操控研究》&#xff0c;这个系列主要用于记录做毕设的过程。 轨迹规划是机器人绕不过去的话题&#xff0c;其目的是为了让机器人的运动更加的平滑。对于四足机器人&#xff0c;贝赛尔曲线的应用比较普遍。而对于机械臂&#xff0c;…

【C++】C++ 入门(一)

目录 一、前言 1、什么是C 2、C关键字(C98) 二、第一个C程序 三、命名空间 1、存在意义 2、命名空间定义 3、命名空间的使用 3.1、指定命名空间访问 3.2、全局展开访问 3.3、部分展开访问 四、C输入&输出 五、缺省参数 1、缺省参数概念 2、缺省参数分类 2.…

【Day4】24两两交换链表中的节点、19删除链表的倒数第N个节点、链表相交、142环形链表Ⅱ

【Day4】24两两交换链表中的节点、19删除链表的倒数第N个节点、160链表相交、142环形链表Ⅱ24.两两交换链表的点19.删除链表的倒数第N个节点160链表相交 面试题02.07142 环形链表Ⅱ判断链表是否有环若链表有环&#xff0c;如何找到环的入口24.两两交换链表的点 题目链接&#…