文章目录

  • 生产者消费者模型
    • 什么是生产者消费者模型
    • 生产者消费者模型的321原则
    • 生产者消费者模型的优点
  • 生产者消费者模型的实现方法
    • 基于循环队列,信号量实现
    • 基于阻塞队列,互斥锁、条件变量实现

生产者消费者模型

什么是生产者消费者模型

生产者消费者模型是针对在任务处理中既要产生数据,又要处理数据这一情景而设计出来的一种解决方案。如果生产者生产资源很快,消费者处理资源的速度很慢,则生产者就必须等待消费者处理完数据才能继续生产,反之同理,这样的话生产者与消费者之间的耦合度较高(依赖关系),导致总体效率较低。
于是通过引入一个交易场所(缓冲区),来解决生产者与消费者之间的强耦合关系生产者与消费者之间不是直接通讯,而是通过这个交易场所来间接通讯,将两者的直接关系转变成间接关系,生产者生产完数据直接交给仓库,而消费者要使用则直接从缓冲区取出数据,这样效率就大大的提高。

生产者消费者模型的321原则

  1. 一个场所
    交易场所:缓冲区

  2. 两个角色
    生产者:生产资源的角色,往交易场所放数据
    消费者:消费资源的角色,从交易场所取数据

  3. 三种关系
    生产者与消费者之间的同步与互斥关系
    生产者与生产者之间的互斥关系
    消费者与消费者之间的互斥关系

生产者消费者模型的优点

  • 解耦合:
    生产者与消费者之间不直接通讯,而是借助交易场所通讯,降低了耦合度
  • 支持忙闲不均:
    可以解决某一方过忙而某一方过闲的问题
  • 支持并发:
    多线程轮询处理

生产者消费者模型的实现方法

基于循环队列,信号量实现

可以借助循环队列来作为交易场所,用信号量实现同步与互斥关系

#include<iostream>
#include<pthread.h>
#include<vector>
#include<semaphore.h>
using namespace std;const size_t MAX_SIZE = 5;class CircularQueue
{public:CircularQueue(int capacity = MAX_SIZE) : _queue(capacity) , _capacity(capacity), _front(0), _rear(0){sem_init(&_mutex, 0, 1);sem_init(&_full, 0, 0);sem_init(&_empty, 0, _capacity);}~CircularQueue(){sem_destroy(&_mutex);sem_destroy(&_full);sem_destroy(&_empty);}//队尾入队void Push(int data){//入队前先减少一个空闲资源,防止其他进程争抢资源,如果资源不够则阻塞sem_wait(&_empty);sem_wait(&_mutex);_queue[_rear++] = data;_rear %= _capacity;sem_post(&_mutex);sem_post(&_full);//入队完成,使用资源+1,同时唤醒消费者使用资源}//队头出队void Pop(int& data){//出队前减少当前使用的资源数sem_wait(&_full);sem_wait(&_mutex);data = _queue[_front++];_front %= _capacity;sem_post(&_mutex);sem_post(&_empty);//出队完成,唤醒生产者生产资源}private:vector<int> _queue;size_t _capacity;size_t _front;size_t _rear;//需要三个信号量,一个互斥信号量,一个信号量表示空闲资源,一个信号量正在使用资源sem_t _mutex;sem_t _full;sem_t _empty;};void* productor(void* arg)
{//因为入口函数的参数为void*,所以需要强转回对应类型CircularQueue* queue = (CircularQueue*)arg;int data = 0;while(1){queue->Push(data);cout << "生产者存入数据:" << data++ << endl;}return NULL;
}void* customer(void* arg)
{CircularQueue* queue = (CircularQueue*)arg;int data;while(1){queue->Pop(data);cout << "消费者取出数据:" << data << endl;}return NULL;
}int main()
{CircularQueue queue;pthread_t pro_pid[5], cus_pid[5];//创建线程for(int i = 0; i < 5; i++){int ret = pthread_create(&pro_pid[i], NULL, productor, (void*)&queue);if(ret){cout << "生产者进程创建失败" << endl;return -1;}ret = pthread_create(&cus_pid[i], NULL, customer, (void*)&queue);if(ret){cout << "消费者进程创建失败" << endl;return -1;}}//线程等待for(int i = 0; i < 5; i++){pthread_join(pro_pid[i], NULL);pthread_join(cus_pid[i], NULL);}return 0;
}

这种方法是教科书上常用的,实现起来也较为简单,但是有一个需要注意的问题,
出队和入队时,互斥信号量一定要放在条件信号量后面,否则会引起死锁问题
这里以生产者举例子,假设如果先执行wait(mutex),此时如果没有空闲的空间,生产者就会阻塞,此时到消费者进程,消费者却因为mutex已经加锁,无法进行后面的post操作使互斥信号量mutex进行解锁,此时消费者也会被阻塞,这时两者都期待对方解锁,但是双方都阻塞,无法解锁,导致程序进入了永眠状态,也就是死锁。

运行效果

基于阻塞队列,互斥锁、条件变量实现

借助阻塞队列作为交易场所,互斥锁实现互斥关系,条件变量实现同步关系

#include<iostream>
#include<unistd.h>
#include<pthread.h>
#include<queue>using namespace std;const size_t MAX_SIZE = 5;class BlockQueue
{public:BlockQueue(size_t capacity = MAX_SIZE) : _capacity(capacity){pthread_mutex_init(&_mutex, NULL);pthread_cond_init(&_pro_cond, NULL);pthread_cond_init(&_cus_cond, NULL);}~BlockQueue(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_pro_cond);pthread_cond_destroy(&_cus_cond);}void Push(int data){//加锁保证线程安全pthread_mutex_lock(&_mutex);//队列已满,阻塞生产者生产//循环等待,防止有其他进程进入while(_queue.size() == _capacity){pthread_cond_wait(&_pro_cond, &_mutex);}//数据入队_queue.push(data);pthread_mutex_unlock(&_mutex);//生产完毕,唤醒消费者消费pthread_cond_signal(&_cus_cond);}void Pop(int& data){pthread_mutex_lock(&_mutex);while(_queue.empty()){//队列空,阻塞消费者消费pthread_cond_wait(&_cus_cond, &_mutex);}//数据出队data = _queue.front();_queue.pop();//消费完毕,唤醒生产者生产pthread_cond_signal(&_pro_cond);pthread_mutex_unlock(&_mutex);}private:queue<int> _queue;size_t _capacity;//实现互斥关系的互斥锁pthread_mutex_t _mutex;//实现同步关系的条件变量pthread_cond_t _pro_cond;pthread_cond_t _cus_cond;
};//因为入口函数的参数必须为void* ,所以要强转成BlockQueue类型
void *producter(void *arg)
{BlockQueue *queue = (BlockQueue*)arg;//生产者不断生产数据int data = 0;while(1){//生产数据queue->Push(data);cout << "生产者放入数据:" << data++ << endl;}return NULL;
}void *customer(void *arg)
{BlockQueue *queue = (BlockQueue*)arg;//消费者不断取出数据while(1){//取出数据int data;queue->Pop(data);cout << "消费者取出数据:" << data << endl;}return NULL;
}int main()
{BlockQueue queue;pthread_t pro_tid[5], cus_tid[5];//创建线程for(size_t i = 0; i < 5; i++){int ret = pthread_create(&pro_tid[i], NULL, producter, (void*)&queue);if(ret){cout << "生产者线程创建失败" << endl;return -1;}ret = pthread_create(&cus_tid[i], NULL, customer, (void*)&queue);if(ret){cout << "消费者线程创建失败" << endl;return -1;}}   //等待线程退出for(size_t i = 0; i < 4; i++){pthread_join(pro_tid[i], NULL);pthread_join(cus_tid[i], NULL);}return 0;
}

在这里我们又会看到,这里是先对互斥锁加锁,再对条件变量进行wait操作,这里为什么不会引起死锁呢?
因为条件变量的pthread_cond_wait操作其实集合了三个操作,他会首先对互斥锁解锁,然后判断条件变量是否达成,没有则阻塞,之后再对互斥锁进行加锁,所以这样就不会因为没人解锁而陷入死锁状态。

实现效果

操作系统:生产者消费者模型的两种实现(C++)相关推荐

  1. 生产者-消费者模型的两种实现方式

    https://www.cnblogs.com/caolicangzhu/p/7086176.html 本文主要来总结生产者-消费者模型的代码实现,至于其原理,请大家自行百度. 一.基于链表的生产-消 ...

  2. 操作系统 —— 生产者消费者模型

    文章目录 1. 生产者消费者模型的理解 1.1 串行的概念 1.2 并行的概念 1.3 简单总结: 2. 基于阻塞队列(block queue)实现此模型 2.1 阻塞队列的实现 2.2 使用阻塞队列 ...

  3. 操作系统 — 生产者消费者模型

    生产者消费者模型 所谓的生产者消费者模型就是一个类似于队列一样的东西串起来,这个队列可以想像成一个存放产品的"仓库",生产者只需要关心这个 "仓库",并不需要关 ...

  4. java生产者消费者流程图_Java 生产者消费者模型的三种实现过程

    生产者一边在生产,消费者一边消耗.当库存满的时候生产者暂停生产,直到有空位:当库存空的时候消费者暂停消费,直到有产品. 关键点: 生产者和消费者都是在不断生产和消费的,是同时并发的,不应该等满了再消费 ...

  5. 生产者消费者模型的三种实现方式

    某个线程或模块的代码负责生产数据(工厂),而生产出来的数据却不得不交给另一模块(消费者)来对其进行处理,在这之间使用了队列.栈等类似超市的东西来存储数据(超市),这就抽象除了我们的生产者/消费者模型. ...

  6. 【1】生产者-消费者模型的三种实现方式

    (手写生产者消费者模型,写BlockingQueue较简便 ) 1.背景                                                                 ...

  7. Java生产者 消费者模型的一种实现

    本文主要介绍java中生产者/消费者模式的实现,对java线程锁机制的一次深入理解. 生产者/消费者模型 生产者/消费者模型要保证,同一个资源在同一时间节点下只能被最多一个线程访问,这个在java中用 ...

  8. 操作系统——生产者消费者模型以及信号量

    生产者--消费者问题是 多个进程因共享一个缓存区而产生的相互依赖问题 .具体来说,生产者进程往往要往共享缓存区中放内容.消费者进程从共享缓存中取内容, 当缓存区满的时候 ,生产者进程需要等待消费者进程 ...

  9. 三、操作系统——生产者-消费者问题(两个同步一个互斥)

    一.问题描述 信号量机制实现进程互斥的步骤: 设置初值为1的互斥信号量 在访问临界区之间进行P操作 在访问完临界区之后进行V操作 信号量机制实现进程同步的步骤: 设置初值为0的同步信号量 在前操作之后 ...

最新文章

  1. php数组包含对象吗,在包含数组的PHP对象上调用方法
  2. 14年阿里巴巴管理经验总监:教你管理7步心法(演讲全文)
  3. 处理字符串_14_SQL处理IN和合并后字符串案例详解
  4. 错误内存【读书笔记】C程序中常见的内存操作有关的典型编程错误
  5. 帆软获取上月的第一天与最后一天_《原神》岩港打工第一天怎么玩 岩港打工第一天玩法攻略...
  6. Windows 10企业批量部署实战之刷新并添加启动映像
  7. context.Request.Files超过了最大请求长度
  8. 金刚爸爸是这么教育金刚儿子的
  9. C#网络编程----使用UdpClient实现网络会议讨论(详解)
  10. 华为全系Visio图标下载链接
  11. Axis1.4容器WebService服务发布过程
  12. 飞思卡尔16位单片机(四)——GPIO输入功能测试
  13. 欧姆龙PLC HostLink协议整理
  14. 酷狗歌词Krc批量转换工具Lrc [附转换编码DLL]
  15. 陈经纶2021年高考成绩查询时间,2020北京市地区高考成绩排名查询
  16. 通用稳定DNS,国际DNS,国内DNS,公共DNS
  17. 关于Xshell无法连接VM中的openEuler的解决思路
  18. 《蒋勋说宋词》 读后感
  19. ios12升级, App应用崩溃闪退
  20. 嵌入式开发(S5PV210)——u-boot的头文件包含问题

热门文章

  1. unity 实现场景过度动画
  2. (一)如何新建一个微信小程序
  3. 解决Linux下Nvidia闭源驱动的双显卡笔记本画面撕裂问题
  4. VBA编程_常用函数总结3
  5. 数据结构与算法-项目实训-贪吃蛇
  6. 电信,请您挺起胸膛赚钱
  7. 什么是SpringCloud?可以用于干什么?
  8. JMeter的官方网站
  9. 关于Word中InsertXML的说明(Word ML)
  10. js 刷新css 样式