《Linux从练气到飞升》No.29 生产者消费者模型

简介: 《Linux从练气到飞升》No.29 生产者消费者模型

前言

并发编程领域,生产者消费者模型是一个经典且重要的话题。它涉及到多线程之间的协作与通信,展现了在复杂系统中保持数据一致性和避免资源竞争的关键技术。通过深入探讨生产者消费者模型,我们可以了解如何利用同步和互斥的机制来实现线程之间的有效协作,从而提高程序的效率和可靠性。

在本篇博客中,我将带领读者逐步理解生产者消费者模型的设计思想、实现方法以及可能遇到的问题。无论是初学者还是有一定经验的开发人员,都可以通过本文深入了解生产者消费者模型,并掌握如何在实际项目中应用这一模型来优化程序结构和性能。

让我们一起探索生产者消费者模型的精妙之处,为并发编程的世界增添新的活力与智慧。

1 相关概念

  1. 321原则

3:3种关系,生产者之间(互斥)、消费者之间(互斥)、生产者和消费者之间(互斥+同步)

2:两种角色,生产者和消费者

1:一个交易场所

  1. 使用生产者消费者模型的原因

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

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

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

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

其与普通的队列区别在于,当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放入了元素;当队列满时,往队列里存放元素的操作也会被阻塞,直到有元素被从队列中取出(以上的操作都是基于不同的线程来说的,线程在对阻塞队列进程操作时会被阻塞)

2 基于BlockingQueue的生产者消费者代码实现

BlockQueue.cc

#pragma
#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<queue>
#include<stdlib.h>
using namespace std;
class Task
{
public:
    int _x;
    int _y;
    Task(){}
    Task(int x,int y)
        :_x(x),_y(y)
    {}
    int run()
    {
        return _x+_y;
    }
    ~Task(){}
};
class BlockQueue
{
private:
    /* data */
    std::queue<Task> q;//设置一个队列
    int _cap;//容量
    pthread_mutex_t lock;//设置一把互斥锁
    pthread_cond_t c_cond;//满了通知消费者
    pthread_cond_t p_cond;//满了通知生产者
    void LockQueue()//加锁
    {
        pthread_mutex_lock(&lock);
    }
    void UnlockQueue()//解锁
    {
        pthread_mutex_unlock(&lock);
    }
    bool IsEmpty()//判断队列是否为空
    {
        return q.size()==0;
    }
    bool IsFull()//判断队列是否满
    {
        return q.size()==_cap;
    }
    void ProductWait()//生产者等待
    {
        pthread_cond_wait(&p_cond,&lock);
    }
    void ConsumerWait()//消费者等待
    {
        pthread_cond_wait(&c_cond,&lock);
    }
    void WakeUpProduct()//唤醒生产者
    {
        pthread_cond_signal(&p_cond);
    }
    void WakeUpConsumer()//唤醒消费者
    {
        pthread_cond_signal(&c_cond);
    }
public:
    BlockQueue(int cap)//初始化
        :_cap(cap)
    {
        pthread_mutex_init(&lock,NULL);
        pthread_cond_init(&c_cond,NULL);
        pthread_cond_init(&p_cond,NULL);
    }
    ~BlockQueue()
    {
        pthread_mutex_destroy(&lock);
        pthread_cond_destroy(&c_cond);
        pthread_cond_destroy(&p_cond);
    }
    void put(Task in)
    {
        LockQueue();
        while(IsFull()){
            WakeUpConsumer();//唤醒消费者
            std::cout<<"queue full,notify consume , stop product"<<std::endl;
            ProductWait();//生产者线程等待
        }
        q.push(in);
        UnlockQueue();
    }
    void get(Task&out){
        LockQueue();
        while(IsEmpty()){
            WakeUpProduct();//唤醒生产者
            std::cout<<"queue Empty,notify product , stop consum"<<std::endl;
            ConsumerWait();//消费者线程等待
        }
        out=q.front();
        q.pop();
        UnlockQueue();
    }
};

main.cc

#include"BlockQueue.cc"
pthread_mutex_t p_lock;
pthread_mutex_t c_lock;
void* Product_Run(void* arg)
{
    BlockQueue* bq = (BlockQueue*)arg;
    srand((unsigned int)time(NULL));
    while(1)
    {
        pthread_mutex_lock(&p_lock);
        int x = rand()%10 + 1;
        int y = rand()%100 + 1;
        Task t(x,y);
        bq->put(t);
        pthread_mutex_unlock(&p_lock);
        cout<<"product data is "<<t.run()<<endl;
    }
}
void* Consumer_Run(void* arg)
{
    BlockQueue* bq = (BlockQueue*)arg;
    srand((unsigned int)time(NULL));
    while(1)
    {
        pthread_mutex_lock(&c_lock);
        Task t;
        bq->get(t);
        pthread_mutex_unlock(&c_lock);
        cout<<"consumer is "<<t._x<<"+"<<t._y<<"="<<t.run()<<endl;
        sleep(1);
    }
}
int main()
{
    BlockQueue* bq = new BlockQueue(10);
    pthread_t c,p;
    pthread_create(&c,NULL,Product_Run,(void*)bq);
    pthread_create(&p,NULL,Consumer_Run,(void*)bq);
    pthread_join(c,NULL);
    pthread_join(p,NULL);
    delete bq;
    return 0;
}

makefile

main:main.cc
  g++ -o $@ $^ -lpthread
.PTHONY:
clean:
  rm -f main

结果:

可以观察到生产者生产任务,消费者完成任务

相关文章
|
7月前
|
缓存 安全 Linux
Linux 五种IO模型
Linux 五种IO模型
|
7月前
|
网络协议 Linux 数据安全/隐私保护
在Linux中,TCP/IP 的七层模型有哪些?
在Linux中,TCP/IP 的七层模型有哪些?
|
7月前
|
Kubernetes Linux API
在Linux中,LVS-DR模型的特性是什么?
在Linux中,LVS-DR模型的特性是什么?
|
7月前
|
负载均衡 算法 Linux
在Linux中,LVS-NAT模型的特性是什么?
在Linux中,LVS-NAT模型的特性是什么?
|
3天前
|
Linux
Linux 常用文件查看命令
`cat` 命令用于连接文件并打印到标准输出,适用于快速查看和合并文本文件内容。常用示例包括:`cat file1.txt` 查看单个文件,`cat file1.txt file2.txt` 合并多个文件,`cat &gt; filename` 创建新文件,`cat &gt;&gt; filename` 追加内容。`more` 和 `less` 命令用于分页查看文件,`tail` 命令则用于查看文件末尾内容,支持实时追踪日志更新,如 `tail -f file.log`。
21 5
Linux 常用文件查看命令
|
4月前
|
Linux 网络安全 数据安全/隐私保护
Linux 超级强大的十六进制 dump 工具:XXD 命令,我教你应该如何使用!
在 Linux 系统中,xxd 命令是一个强大的十六进制 dump 工具,可以将文件或数据以十六进制和 ASCII 字符形式显示,帮助用户深入了解和分析数据。本文详细介绍了 xxd 命令的基本用法、高级功能及实际应用案例,包括查看文件内容、指定输出格式、写入文件、数据比较、数据提取、数据转换和数据加密解密等。通过掌握这些技巧,用户可以更高效地处理各种数据问题。
433 8
|
1月前
|
Linux
Linux系统之whereis命令的基本使用
Linux系统之whereis命令的基本使用
77 24
Linux系统之whereis命令的基本使用
|
9天前
|
Linux
Linux od命令
本文详细介绍了Linux中的 `od`命令,包括其基本语法、常用选项和示例。通过这些内容,你可以灵活地使用 `od`命令查看文件内容,提高分析和调试效率。确保理解每一个选项和示例的实现细节,应用到实际工作中时能有效地处理各种文件查看需求。
43 19
|
20天前
|
缓存 Ubuntu Linux
Linux中yum、rpm、apt-get、wget的区别,yum、rpm、apt-get常用命令,CentOS、Ubuntu中安装wget
通过本文,我们详细了解了 `yum`、`rpm`、`apt-get`和 `wget`的区别、常用命令以及在CentOS和Ubuntu中安装 `wget`的方法。`yum`和 `apt-get`是高层次的包管理器,分别用于RPM系和Debian系发行版,能够自动解决依赖问题;而 `rpm`是低层次的包管理工具,适合处理单个包;`wget`则是一个功能强大的下载工具,适用于各种下载任务。在实际使用中,根据系统类型和任务需求选择合适的工具,可以大大提高工作效率和系统管理的便利性。
111 25