当前位置: 首页 > news >正文

Linux:多线程---深入生产消费模型环形队列生产消费模型

文章目录

      • 1. 生产者消费者模型
        • 1.1 深入生产消费模型
        • 1.2 条件变量误唤醒
      • 2. POSIX信号量
        • 2.1 信号量的概念
        • 2.2 信号量的接口
      • 3. 环形队列生产消费模型
        • 3.1 环形队列的概念

  • 序:在上一章中,我们通过同步的概念了解了条件变量的概念,并且对生产者消费者模型有了一定的认识,但仅仅是这样,我们对于生产者消费者模型的认识还是太浅显了,所以本章将深入生产者消费者模型,并用POSIX信号量来实现环形队列生产者消费者模型

1. 生产者消费者模型

1.1 深入生产消费模型

问题一:生产者的数据从哪里来?

用户,网络等,生产者生产的数据也是要花时间的!
消费者要不要对拿到的数据进行数据加工处理?要的,这也是要花时间的。

在这里插入图片描述

生产者:
1. 获取数据
2.生产数据到队列
消费者:
1. 消费数据
2. 对数据进行加工处理

问题二:生产消费模型为什么高效?生产消费模型,不是生产几个,然后消费几个吗?哪怕有不同的生产和消费策略,但是这哪里高效了?

当生产者持有锁时,他在生产数据,此时消费者难道就只能等待生产者生产数据到一个临界点,甚至生产满了吗?答案显然不是,消费者可以在未持有锁的阶段去将之前消费的数据进行加工处理呀!!!同理,当消费者持有锁时,生产者也不会傻傻的等待消费者将数据消费完,生产者会去获取数据!!!等到生产者持有锁时,再接着生产数据到队列内!!!所以这才是生产消费模型高效的原因,无论是生产者还是消费者,他们每时每刻都在完成任务,将空闲的时间利用上,这就是高效!!!

1.2 条件变量误唤醒

问题一:如果线程wait时,被误唤醒了呢??

在这里插入图片描述

假设此时最多能够容纳10个资源,现在已经有9个了,如果调用pthread_cond_broadcast函数唤醒全部线程,三个线程就会对锁展开竞争,其中一个线程就会持有锁,然后生产一个资源后再释放锁,而另外两个线程此时已经不在等待队列了,而是在锁的队列下进行等待,此时锁被释放,两个线程中的其中一个就会得到锁,继续执行生产资源的任务,然后此时的资源已经满了(这种情况就叫做伪唤醒或者误唤醒),此时的另外两个线程,拿到锁后,并不是从位置1开始进行的,而是直接从位置2开始执行代码,这就会导致没有进行判断这一步,因为多个线程被唤醒后,这些线程就不在条件变量下等待了,所以访问的有序性也就没了。

而要解决这个问题,也很简单,只需要将if的判断条件换成while,这样即使是有伪线程,也会让伪线程再次进行判断,然后再次进入等待队列中进行等待。

2. POSIX信号量

2.1 信号量的概念

POSIX信号量和SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。 但POSIX可以用于线程间同步。

2.2 信号量的接口

初始化信号量:

对信号量进行初始化
在这里插入图片描述
第一个参数:传入系统定义的数据结构sem_t
第二个参数:是否为线程间共享,直接默认传0
第三个参数:表示信号量的数量(本质上是计数器)

销毁信号量:

释放信号量资源
在这里插入图片描述
第一个参数:直接传系统定义的数据结构sem_t

等待信号量:

获取信号量资源,对应信号量进行减减操作 — P操作
在这里插入图片描述
这里只讨论sem_wait函数,第一个参数直接传系统定义的数据结构sem_t

发布信号量:

释放信号量资源,对信号量进行加加操作 — V操作
在这里插入图片描述
第一个参数直接传系统定义的数据结构sem_t

信号量的使用:p()操作 —> //访问资源 —> V()操作 —> //释放资源

3. 环形队列生产消费模型

3.1 环形队列的概念

用数组模拟环形队列,有tail和head,head位置是添加元素的位置,满了和空的时候,tail和head指向的是同一个位置,所以无法判断是空还是满,第一种方法是添加计数器,第二种方法是空一个位置,加个判断temp=head+1;temp%20;判断head的下一个位置是不是tail,如果是就生产满了,就不允许生产了,反之则可以生产

在这里插入图片描述
其中消费者和生产者都是顺时针进行生产和消费,并且满足以下规则:

1. 指向同一个位置,只能由一个人访问(空:只能由生产者来访问环形队列,满:只能由消费者来访问环形队列)
2. 消费者不能超过生产者
3. 生产者不能把消费者套一个圈

只有当空和满的时候,生产者和消费者才会在同一个位置,当不空和不满的时候,生产者和消费者就不在同一个位置。
生产者关注什么资源?关注环形队列还有多少剩余空间(最开始SpaceSem:N)
消费者关注什么资源?关注环形队列还有多少剩余数据(最开始DataSem:0)

环形队列代码如下:

#pragma once#include<iostream>
#include<vector>
#include<semaphore.h>template<class T>
class RingQueue
{
const static int defuatcap = 50;
private:void P(sem_t &sem){sem_wait(&sem);}void V(sem_t &sem){sem_post(&sem);}void Lock(pthread_mutex_t &mutex){pthread_mutex_lock(&mutex);}void Unlock(pthread_mutex_t &mutex){pthread_mutex_unlock(&mutex);  }public:RingQueue(int cap = defuatcap):_ringqueue(cap),_cap(cap),_c_step(0),_p_step(0){pthread_mutex_init(&_c_mutex,nullptr);pthread_mutex_init(&_p_mutex,nullptr);sem_init(&_c_data_sem,0,0);sem_init(&_p_space_sem,0,_cap);}void Push(const T &in)//生产{P(_p_space_sem);Lock(_p_mutex);_ringqueue[_p_step] =in;//位置后移,维持环形特征_p_step++;_p_step%=_cap;Unlock(_p_mutex);V(_c_data_sem);}void Pop(T *out)//消费{P(_c_data_sem);Lock(_c_mutex);*out = _ringqueue[_c_step];//位置后移,维持环形特征_c_step++;_c_step%=_cap;Unlock(_c_mutex);V(_p_space_sem);}~RingQueue(){pthread_mutex_destroy(&_c_mutex);pthread_mutex_destroy(&_p_mutex);sem_destroy(&_c_data_sem);sem_destroy(&_p_space_sem);}private:std::vector<T> _ringqueue;int _cap;int _c_step;//消费者下标int _p_step;//生产者下标sem_t _c_data_sem;//消费者关注的数据资源sem_t _p_space_sem;//生产者关注的空间资源pthread_mutex_t _p_mutex;pthread_mutex_t _c_mutex;
};

当环形队列为空时,只能进行生产动作,不能进行消费动作
当环形队列为满时,只能进行消费动作,不能进行生产动作
当环形队列不空不满时,生产者和消费者就实现了并发访问,能够同时进行

其中,对于先加锁还是先申请信号量,有两种方案:
方案一
在这里插入图片描述

方案二
在这里插入图片描述

首先,信号量的操作是原子的,是不需要加锁的,临界区代码能少就少,其次,如果先申请锁再申请信号量,如果信号量没有申请到,就会白费时间,而如果先进行申请信号量再申请锁,假设,有一个线程已经申请到锁了,正在进行生产,此时其他线程进来了,就不会干等待,等待锁的就绪,而是会先申请信号量,如果申请到了,只要锁一释放就能立马进行生产操作!!!,只有抢到了信号量资源,才需要被加锁,如果连信号量资源都没有申请到,就没必要加锁,加锁也是很耗资源的,所以选择第二个方案。
通俗点讲,如果将信号量比作票数,锁比作一个门,如果先排队,到门口再进行买票,就会出现排了半天发现没票了,进不去门里面,浪费了时间,而如果先买了票,让买了票的人进行排队,只要排到了就能立马进入门里面。

当环形队列不为空和不为满的时候,生产者和消费者只需要维持自身的互斥关系,不需要考虑对方。

总结:

本章深入探讨了生产者消费者模型,引入了生产者获取生产资料和消费者对消费数据进行加工的概念,而后又通过信号量实现了环形队列下的生产消费模型,深入探讨了加锁和PV操作的优先级的问题。

http://www.lryc.cn/news/593221.html

相关文章:

  • Nestjs框架: RxJS 核心方法实践与错误处理详解
  • 告别项目混乱:基于 pnpm + Turborepo 的现代化 Monorepo 工程化最佳实践
  • win10连接鼠标自动关闭触摸板/win10关闭触摸板(笔记本)
  • 深度学习图像分类数据集—六十种植物病害分类
  • ABP VNext + Temporal:分布式工作流与 Saga
  • install_arm_docker.sh
  • Django接口自动化平台实现(三)
  • Django母婴商城项目实践(八)- 数据渲染与显示之首页
  • LLM 的Top-P参数 是在LLM中的每一层发挥作用,还是最后一层?
  • 【设计模式C#】外观模式(用于解决客户端对系统的许多类进行频繁沟通)
  • Django母婴商城项目实践(七)- 首页数据业务视图
  • 洛谷 P2947:[USACO09MAR] Look Up S ← 数组模拟+单调栈
  • 使用 Gunicorn 部署 Django 项目
  • 5 基于STM32单片机的绝缘检测系统设计(STM32代码编写+手机APP设计+PCB设计+Proteus仿真)
  • 6 STM32单片机的智能家居安防系统设计(STM32代码+手机APP设计+PCB设计+Proteus仿真)
  • 对话访谈 | 盘古信息×锐明科技:中国企业高质量出海“走进去”和“走上去”
  • 家庭KTV v1.1.9 | 曲库丰富,无限制免费K歌
  • 驾驭 Spring Boot 事件机制:8 个内置事件 + 自定义扩展实战
  • 《一行注解解决重复提交:Spring Boot 接口幂等实战》
  • 深入理解设计模式:策略模式的艺术与实践
  • 在非Spring Boot的Spring项目中使用Lock4j
  • 用graphviz画一个关系图
  • 云服务器磁盘IO性能优化的测试与配置方法
  • 2025年7月19日,二维矩阵
  • 智能制造——解读39页汽车行业数字化工厂解决方案【附全文阅读】
  • 异世界历险之数据结构世界(二叉树-leetcode)
  • 国产电科金仓数据库:融合进化,智领未来
  • 【Unity3D实例-功能-移动】角色移动-通过WSAD(Rigidbody方式)
  • 架构探索笔记【1】
  • JavaScript空值安全深度指南