基于信号量的生产者消费者模型

文章目录

  • 信号量
    • 认识概念
    • 基于线程分析信号量
    • 信号量操作
  • 循环队列下的生产者消费者模型
    • 理论认识
    • 代码部分

信号量

认识概念

信号量本质:
计数器
它也叫做公共资源

为了线程之间,进程间通信------>多个执行流看到的同一份资源---->多个资源都会并发访问这个资源(此时易出现覆盖)---->数据不一致的问题----->保护起来---->互斥 和 同步

总结:解决问题的同时会伴随新问题的发生
互斥:任何时候只允许一个执行流(进程)访问公共资源,加锁完成
同步:多个执行流执行的时候,按照一定的顺序执行

被保护起来的资源,临界资源(如,管道)<------->非临界资源
访问该临界资源的代码叫做临界区<----->非临界区

维护临界资源就是维护临界区

原子性:只有两态,对于一件事要么没做要么就做完了(操作系统中)

如何理解信号量?
看电影为例,买票座位属于你,还是坐上去座位就属于你?
但有人占座,这个时候电影院内部的座位,就是多人共享的资源------公共资源
买票的本质: 是对资源的预订机制

有100个座位就绝对不会卖出101张票,如何做到这一点?维护一个计数器,int count = 100;//表示公共资源的个数

在这里插入图片描述
信号量:表示对资源数目的计数器,每一个执行流系想访问公共资源内的谋一份资源,不应该让执行流直接访问
而是先申请信号量资源.先对信号量计数器进行–操作,只要–成功了,就完成了对资源的预定机制
如果申请不成功,执行流被挂起阻塞

对于公共资源,在他的前面添加
在这里插入图片描述对于信号量为1的公共资源
int sem=1;
二元信号量—互斥锁—完成互斥功能
只能有一个人成功 ,其他执行流(进程)访问会出现阻塞

这样的表明允许访问公共资源的计数叫做信号量(信号灯)

分析细节问题:

1.每个进程都要看到同一个信号量资源-----就只能由OS提供,在IPC体系

2.信号量本质也是公共资源(还好信号量的访问并不复杂,只有-- ++ 的操作,如果访问出错,只要阻塞就可以)
这个操作也属于原子性
- - P
++ V
为什么不定义一个int来完成这个操作?
访问数据的不同进程间的操作,包含大量的拷贝等工作,所以单靠一个int不行

3.单个信号量(目前就这么理解)
struct sem
{
int count;//计数器
task_struct *wait_queue;//等待队列,对这个队列里面的进程进行操作(类似需要操作就对他进行)
}

基于线程分析信号量

现在基于线程概念再来整体理解一下:

1.信号量的本质是一把计数器
2.申请信号的本质就是预订资源
3.pv操作是原子的

怎么理解?

在上篇写的阻塞队列是一个公共资源,同时他的访问是一个整体形式去访问
现在假设这个公共资源是一个数组,那么不同线程可以访问不同数组的不同索引由此达到共同访问临界资源的目的.假设这个数组就是大小为7,有8个线程访问这个临界资源就会出现问题,所以这个时信号量就会起作用,他就相当于是一个计数器

所以上述情况,线程在访问之前都会先申请一个信号量,然后访问指定的一个位置(程序员进行维护),访问结束,释放信号量

这时,对于资源的访问判断已经由信号量充当了,所以不需要进行资源就绪的判断信号量申请成功,这个资源就一定能访问,这个也是原子性的
信号量为1,表示这个资源整体就是只能一个线程进行访问,这边就是互斥的

信号量操作

信号量的操作:
1.快速认识接口
信号量创建:

在这里插入图片描述

参数1:定义一个类似pthread_t类型的变量,这个类型在这边是sem_t类型,
参数2:在一个进程的线程之间共享(0表示这个种情况)还是在进程之间共享
参数3:这个信号量计数器的初始值
信号量销毁:

在这里插入图片描述
销毁信号量
信号量申请,也可以说是信号量等待(阻塞等待,有信号量才会进行后续操作,信号量的值减1)

在这里插入图片描述
这边只讨论普通阻塞方式
发布信号量:

在这里插入图片描述
表示信号量资源使用完毕,可以归还资源,将信号量的值+1

循环队列下的生产者消费者模型

理论认识

基于环形队列的生产者消费者问题–理论
环形队列:
逻辑上为环状,入队列出队列为同一个位置
判空判满可以加一个计数器或者是消耗一个空间的方式进行
这边不做讨论,因为信号量的操作就能完成判定
在这里插入图片描述
在循环队列中,

1.生产者不能把消费者套一个圈
2.消费者不能超过生产者

消费者生产者在这个结构中也是一样,为空或者为满会指向同一个位置,这个时候就不能并发访问,在其他情况下可以去并发访问,也就是并发进入临界区.

所以在为空 为满为互斥,生产者 消费者跑是同步,这是需要局部维持的"资源"的认识:
P:空间是资源(无空间了,不生产)
c:数据是资源(无数据了,不消费)
所以需要两个信号量来维护这个资源

伪代码:

p->sem_space:N
c->sem_data:0
生产者:p(sem_space)//预定资源,申请空间资源用来生产//生产行为,位置占据v(sem_data)//告诉消费者可以进行消费		
消费者:p(sem_data)//申请数据资源用来消费//消费行为v(sem_space)//告诉生产者当前位置为空可以来生产

代码部分

单任务的循环队列式生产者消费者模型
main.cc

#include "RingQueue.hpp"
#include <unistd.h>
void *Productor(void *args)
{RingQueue<int> *rq = static_cast<RingQueue<int> *>(args);int cnt = 100;while(true){rq->Push(cnt);std::cout << "Productor done, the data is " << cnt << std::endl;cnt--;}
}
void *Consumer(void *args)
{RingQueue<int> *rq = static_cast<RingQueue<int> *>(args);while(true){int data = 0;rq->Pop(&data);std::cout << "Consumer done, the data is " << data << std::endl;sleep(1);}}
int main()
{pthread_t c, p;RingQueue<int> *rq = new RingQueue<int>();pthread_create(&p, nullptr, Productor, rq);pthread_create(&c, nullptr, Consumer, rq);   pthread_join(p, nullptr);pthread_join(c, nullptr);return 0;
}

RingQueue.hpp

#pragma once
#include <iostream>
#include <vector>
#include <semaphore.h>
#include <pthread.h>const int defaultsize = 5;
template<class T>
class RingQueue
{
private:void P(sem_t &sem)//用于申请可以访问的资源{sem_wait(&sem);}void V(sem_t &sem)//资源使用完毕{sem_post(&sem);}
public:RingQueue(int size = defaultsize):_ringqueue(size), _size(size), _p_step(0), _c_step(0){sem_init(&_space_sem, 0, size);sem_init(&_data_sem, 0, 0);}void Push(const T& in)//生产者放入数据{P(_space_sem);_ringqueue[_p_step] = in;_p_step++;_p_step %= _size;V(_data_sem);}void Pop(T *out){P(_data_sem);*out = _ringqueue[_c_step];_c_step++;_c_step %= _size;V(_space_sem);}~RingQueue(){sem_destroy(&_space_sem);sem_destroy(&_data_sem);}
private:std::vector<T> _ringqueue;int _size;sem_t _space_sem;sem_t _data_sem; int _p_step;int _c_step;
};

引入任务后的生产者消费者模型
在原来基础上,将处理数据从int变为实际的Task,这边的Task只是一个算术运算,实际需求可根据实际情况去更改
在这里插入图片描述

即 将模板参数和生产者 消费者的实际内容进行修改
多生产 多消费内容的修改

与单生产,单消费的区别在于需要考虑生产者与生产者之间,消费者与消费者之间的关系
利用加锁的方式,让消费者之间可以进行不冲突的添加任务,消费者也是如此

所以在普遍情况下,各个生产者之间,消费者之间是互斥关系
先加锁还是先申请信号量?

答案是先分配信号量再申请锁,这样比较快,这就好比是先买票再排队,而不是排队到你之后再买票,后面的人还要等你买票,时间消耗大

main函数内部:
在这里插入图片描述productor和consumer

在这里插入图片描述
这样一个基于信号量的多线程生产者消费者任务就完成了.

关注我,虾片更精彩~~

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

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

相关文章

【Linux】进程(9):进程控制2(进程等待)

大家好&#xff0c;我是苏貝&#xff0c;本篇博客带大家了解Linux进程&#xff08;9&#xff09;进程控制2&#xff0c;如果你觉得我写的还不错的话&#xff0c;可以给我一个赞&#x1f44d;吗&#xff0c;感谢❤️ 目录 一. 为什么要进程等待二. 如何进行进程等待1.wait函数—…

使用linux的mail命令发送html格式的邮件

1、关闭本机的sendmail服务或者postfix服务 #执行下面的命令&#xff0c;各位大侠都对号入座吧 #sendmial service sendmail stop chkconfig sendmail off #postfix service postfix stop chkconfig postfix off#再狠一点就直接卸载吧.. yum remove sendmail yum remove postf…

欧拉部署nginx

1.下载nginx 下载地址&#xff1a;https://nginx.org/en/download.html 选择稳定版本 下的镜像文件进行下载 2.解压Nginx包 cd /root/nginx tar -zxvf nginx-1.26.0.tar.gz cd nginx-1.26.03.安装nginx相关依赖 yum -y install gcc zlib zlib-devel pcre-devel openssl o…

如何在 CentOS 中配置 Linux 命名空间(ip netns)

引言 Linux 命名空间是一项强大的技术&#xff0c;允许在同一系统上创建多个独立的虚拟化实例&#xff0c;每个实例可以拥有自己的网络栈、路由表、IP 地址等网络资源&#xff0c;实现资源的隔离和管理。本文将深入探讨如何在 CentOS 中配置和使用 ip netns 命名空间&#xff0…

【面试题】正向代理和反向代理的区别?

正向代理&#xff08;Forward Proxy&#xff09;和反向代理&#xff08;Reverse Proxy&#xff09;是两种常见的代理服务器类型&#xff0c;它们在网络通信中扮演着不同的角色&#xff0c;具有不同的功能和应用场景。 一、正向代理 1. 定义与位置 正向代理是位于客户端和目标…

TextView 实现最后一行缩进指定距离

实现图上类似的效果。 指定最大行数为三行&#xff0c;最后一行缩进指定的距离。 如果行数小于三行&#xff0c;则不缩进。 同时文字两端对齐 代码里的 JustifyTextView &#xff08;两端对齐的 Textview &#xff09;详见 Android Textview 多行文本两端对齐_android tex…

Go语言入门之基础语法

Go语言入门之基础语法 1.简单语法概述 行分隔符&#xff1a; 一行代表一个语句结束&#xff0c;无需写分号。将多个语句写在一行可以用分号分隔&#xff0c;但是不推荐 注释&#xff1a; // 或者/* */ 标识符&#xff1a; 用来命名变量、类型等程序实体。 支持大小写字母、数字…

k8s核心操作_Deployment的扩缩容能力_Deployment自愈和故障转移能力---分布式云原生部署架构搭建022

然后我们上面说了k8s中的deployment的多副本能力 然后,我们再来看 k8s中的deployment的扩缩容能力 可以看到,对于扩容,要使用 kubectl scale 命令 对于缩容 要使用kubectl scale 命令都是使用这个命令对吧 来试试,可以看到上面命令 首先看看 kubectl get pod 可以看到有…

第58期 | GPTSecurity周报

GPTSecurity是一个涵盖了前沿学术研究和实践经验分享的社区&#xff0c;集成了生成预训练Transformer&#xff08;GPT&#xff09;、人工智能生成内容&#xff08;AIGC&#xff09;以及大语言模型&#xff08;LLM&#xff09;等安全领域应用的知识。在这里&#xff0c;您可以找…

网络编程:TCP

一、tcp编程 注意 1.数据本身有顺序 2.发送和接收次数不需要对应 3. 1. C/S 模式 》服务器/客户端模型 server:socket()-->bind()--->listen()-->accept()-->recv()-->close() client:socket()-->connect()-->send()-->close(); int on 1; setso…

常用的设计模式和使用案例汇总

常用的设计模式和使用案例汇总 【一】常用的设计模式介绍【1】设计模式分类【2】软件设计七大原则(OOP原则) 【二】单例模式【1】介绍【2】饿汉式单例【3】懒汉式单例【4】静态内部类单例【5】枚举&#xff08;懒汉式&#xff09; 【三】工厂方法模式【1】简单工厂模式&#xf…

GuLi商城-商品服务-API-品牌管理-OSS获取服务端签名

新建第三方服务: 引入common 把common中oss的依赖都拿到第三方服务中来 配置文件: 加上nacos注解:<

HTML 标签简写和全称及其对应的中文说明和实例

<!DOCTYPE html> <html lang"zh-CN"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>HTML 标签简写及全称</title><style>…

Android 通知访问权限

问题背景 客户反馈手机扫描三方运动手表&#xff0c;下载app安装后&#xff0c;通知访问权限打不开。 点击提示“受限设置” “出于安全考虑&#xff0c;此设置目前不可用”。 问题分析 1、setting界面搜“授予通知访问权限”&#xff0c;此按钮灰色不可点击&#xff0c;点…

大小端详解

引例 我们知道整形(int)是4个字节&#xff0c;例如随便举个例子&#xff1a;0x01020304&#xff0c;它一共占了四个地址位&#xff0c;01,02,03,04分别占了一个字节&#xff08;一个字节就对应了一个地址&#xff09;。 那么就会有个问题&#xff1a;我们的01到底是存储在高地…

mysql 5.7.44 32位 zip安装

前言 因为研究别人代码&#xff0c;他使用了5.7的 32位 mysql &#xff0c;同时最新的 8.4 64位 mysql 不能用官方lib连接。所以安装这个版本使用&#xff0c;期间有些坑&#xff0c;在这里记录一下。 下载路径 mysql官方路径&#xff1a;https://downloads.mysql.com/archi…

Linux——多线程(五)

1.线程池 1.1初期框架 thread.hpp #include<iostream> #include <string> #include <unistd.h> #include <functional> #include <pthread.h>namespace ThreadModule {using func_t std::function<void()>;class Thread{public:void E…

Redis 7.x 系列【21】主从复制

有道无术&#xff0c;术尚可求&#xff0c;有术无道&#xff0c;止于术。 本系列Redis 版本 7.2.5 源码地址&#xff1a;https://gitee.com/pearl-organization/study-redis-demo 文章目录 1. 概述2. 工作原理2.1 建立连接2.2 全量复制2.3 命令传播2.4 增量复制 3. 拓扑架构3.…

Uniapp表单提交

template中&#xff1a; <template><view class""><button class"tianjia" click"tianjia">添加</button><view class"divOne" v-show"a"><text class"guanbi" click"gua…

本地 HTTP 文件服务器的简单搭建 (deno/std)

首发日期 2024-06-30, 以下为原文内容: 在本地局域网搭建一个文件服务器, 有很多种方式. 本文介绍的是窝觉得比较简单的一种. 文件直接存储在 btrfs 文件系统之中, 底层使用 LVM 管理磁盘, 方便扩容. 使用 btrfs RAID 1 进行镜像备份 (一个文件在 2 块硬盘分别存储一份), 防止…