Linux 生产者和消费者问题

news2025/4/16 13:40:31

一、相关概念:

1.耦合:耦合是指两个或两个以上的体系或两种运动形式间通过相互作用而彼此影响以至联合起来的现象。在软件工程中,对象之间的耦合度就是对象之间的依赖性。对象之间的耦合越高,维护成本越高,因此对象的设计应使类和构件之间的耦合最小。

2.耦合性:耦合性是程序结构中各个模块之间相互关联的度量。它取决于各个模块之间的接口的复杂程度、调用模块的方式以及哪些信息通过接口。

3.解耦:字面意思就是解除耦合关系。在软件工程中,降低耦合度即可以理解为解耦,模块间有依赖关系必然存在耦合,理论上的绝对零耦合是做不到的,但可以通过一些现有的方法将耦合度降至最低。设计的核心思想就是尽可能减少代码耦合,如果发现代码耦合,就要采取解耦技术。原则就是A功能的代码不要写在B的功能代码中,如果两者之间需要交互,可以通过接口,通过消息,甚至可以引入框架,但总之就是不要直接交叉写。

二、生产者和消费者问题简述

生产者是写入数据,消费者是读取数据,消费者读取一个数据之后,这个数据就没有了,相当于一个有限缓冲区的问题。如下图:

在这里插入图片描述

1.缓冲区的作用相当于解耦

如果两个进程之间直接交换数据的话,一个生产者进程对消费者进程给出数据,但是消费者进程还没有机会去读取,这时生产者进程就会发生阻塞,等待消费者进程来读取数据,两个进程之间相互影响,具有一定的耦合性。通过上面这样一个缓冲区来交换数据的话就可以做到解耦。

有了这样一个缓冲区,如果再增加一个消费者进程来读取数据,生产者进程的代码也不用修改,对生产者进程也没有什么很大的影响,只是生产者进程产生数据的速度会变快。也不会因为增加了一个消费者进程来读取数据,而这时生产者进程还没有产生数据,使消费者进程发生阻塞,因为生产者进程产生的数据已经放到了缓冲区中,消费者进程可以随时来读取。

当然如果缓冲区被生产者进程产生的数据写满了,这时生产者进程就会被阻塞住,反之,如果缓冲区为空,消费者进程想要读取数据,那么消费者进程就会被阻塞。所以,一定要注意缓冲区满和缓冲区空的情况,缓冲区满的时候生产者不可以写入数据,缓冲区空的时候消费者不可以读取数据。

2.生产者向缓冲区写入数据和消费者从缓冲区读取数据的过程

假如现在有n个生产者(写入操作),m个消费者(读取操作),它们都要去操作这块缓冲区空间,要注意不能有两个或多个生产者同时写数据,也不能两个或多个消费者同时读数据,因为如果两个或多个生产者或消费者同时写入或者读取数据,会操作同一块缓冲区空间的数据,这里的消费者读取数据是相当于把数据读走了,也就是把这个数据清理掉了,会对别的进程产生影响,所以不可以两个或多个消费者同时去读取缓冲区中数据。

首先生产者和消费者对缓冲区的访问是一个互斥性访问,所以不管是生产者往缓冲区写入数据还是消费者从缓冲区读取数据,都需要通过互斥锁来控制线程的同步。然后,因为两个或多个生产者同时向缓冲区写入数据,也不能有两个或多个消费者从缓冲区读取数据,所以这里需要通过信号量来控制同步。但是要注意设置互斥锁和信号量的先后顺序。

3.生产者和消费者对缓冲区设置互斥锁和信号量的思路

(1)设置互斥锁

对于互斥锁,无论是消费者还是生产者都应该先看看自己可不可以对缓冲区进行操作,如果是生产者要先看缓冲区有没有剩余空间,如果有剩余空间然后再去加锁,加了锁之后就可以保证只有自己在向缓冲区中写入数据;如果是消费者要先看缓冲区有没有数据,如果有数据再去加锁,加了锁之后就可以保证只有自己在读取缓冲区中的数据。比如,消费者不可以先对缓冲区加锁,因为如果加了所之后再去读取数据的时候发现缓冲区没有数据,消费者就会被阻塞,但是这时消费者已经对缓冲区加过锁了,生产者也不可以向缓冲区中写入数据,也会被阻塞。

(2)设置信号量

对于信号量,需要设置两个信号量,其中一个去控制生产者可不可以向缓冲区写入数据,另一个控制消费者可不可以从缓冲区读取数据。控制生产者的信号量的值要参考缓冲区空间的数目,要看缓冲区空间的数目是几,生产者信号量的值就是几,因为可能不只有一个生产者,会有多个生产者。就比如如果把控制生产者的信号量的值设置为1,生产者要向缓冲区写入数据,P操作就会把信号量的值改为0,进行写入数据的操作,这时如果另外一个生产者也要像缓冲区中写入数据,就会失败,因为此时信号量的值为0,不能再执行P操作了。控制消费者的信号量的值则应该为0,默认情况下缓冲区没有数据,所有消费者都读取不了数据。如下图:

在这里插入图片描述

4.生产者和消费者对缓冲区设置互斥锁和信号量的方法

对于生产者来讲,要看现在缓冲区有没有空间去写入数据,生产者首先要对sc_sem这个信号量进行P操作,如果P操作失败,就说明当前缓冲区满,需要等待,如果P操作成功,生产者每次向缓冲区中写入数据,sc_sem这个信号量的值就减1,执行完P操作之后,就要向缓冲区中写入数据,这时候要防止别的生产者也要向缓冲区写入数据,所以接下来就要定义一个互斥锁,执行加锁操作,生产者向缓冲区中写完数据之后,要进行解锁操作,这时候其它想要向缓冲区写入数据的生产者就可以继续写入数据了,当所有要向缓冲区写入数据的生产者都写完数据之后,对xf_sem这个信号量进行V操作,xf_sem这个信号量的值就加1,xf_sem这个信号量的值从0变为1,这时候消费者就可以工作(从缓冲区读取数据)了。代码的思路应该是这样的:

p(sc_sem)
lock(mutex)
write
unlock(mutex)
v(xf_sem)

对于消费者来讲,要看当前的缓冲区中有没有数据可以读取,消费者首先要对信号量xf_sem进行P操作,如果此时信号量xf_sem为0,也就意味着缓冲区是空的,P操作执行失败,消费者就会被阻塞,如果信号量xf_sem不为0,消费者才可以对信号量xf_sem进行P操作成功,信号量xf_sem的值减1,这时消费者才可以从缓冲区读取数据,这时要防止别的消费者也要从缓冲区中读取数据以及生产者想要向缓冲区中写入数据,所以要对缓冲区加锁,加锁之后,消费就可以从缓冲区中读取数据,读完数据之后进行解锁,这时其他想要从缓冲区读取数据的消费者就可以继续读取数据了,等全部想要读取数据的消费者读取完之后,缓冲区就有空闲的空间了,这时对信号量sc_sem进行V操作,信号量sc_sem的值就加1,这时候生产者就可以继续对信号量sc_sem进行P操作,重复上面生产者向缓冲区写入数据的过程。代码的思路应该是这样的:

p(xf_sem)
lock(mutex)
read
unlock(mutex)
v(sc_sem)

三、生产者和消费者问题代码示例

假设现在有2个生产者,3个消费者,有一个大小为30的缓冲区。每个生产者向缓冲区写入30次数据,有2个生产者,一共写入60次数据,每个消费者从缓冲区读取20次数据,有3个消费者,一共读取60次数据。生产者一共向缓冲区写入60次数据,消费者一共从缓冲区读取了60次数据,这个过程结束之后,缓冲区为空。

代码如下:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<string.h>
#include<semaphore.h>

#define BUFF_MAX 30 //缓冲区的大小


int buff[BUFF_MAX]; //缓冲区

int in = 0;//写入数据的下标
int out = 0;//读取数据的下标

pthread_mutex_t mutex;//定义一个锁变量
sem_t sc_sem;//定义一个信号量,用来控制生产者
sem_t xf_sem;//定义一个信号量,用来控制消费者

void* sc_fun(void*arg)//生产者线程函数
{
    for(int i=0;i<30;i++)//一个生产者线程写入30次,有2个生产者,一共写入60次数据
    {
        sem_wait(&sc_sem);//对控制生产者的信号量sc_sem进行P操作
        pthread_mutex_lock(&mutex);//加锁

        //随机产生一个数据再写入
        buff[in]=rand()%100;//产生一个100以内的随机数,将其写入缓冲区
        printf("生产者向缓存区中写入数据%d,在%d下标写入\n",buff[in],in);
        in=(in+1)%BUFF_MAX;//更新in的下标
        pthread_mutex_unlock(&mutex);//解锁
        sem_post(&xf_sem);//对控制消费者的信号量xf_sem进行V操作
        int n=rand()%3;//产生一个3以内的随机数
        sleep(n);/*睡眠3秒以内,为了不让当前的生产者继续对信号量sc_sem进行P操作,
                   给其他生产者向缓冲区写入数据的机会,让结果看着更随机*/
    }
}
void* xf_fun(void* arg)//消费者线程函数
{
    for(int i=0;i<20;i++)//一个消费者线程读取20次,有3个消费者,一共读取60次数据
    {
        sem_wait(&xf_sem);//对控制消费者的信号量xf_sem进行P操作
        pthread_mutex_lock(&mutex);//加锁
        printf("消费者从缓冲区读取数据%d,在%d下标读取\n",buff[out],out);
        out=(1+out)%BUFF_MAX;
        pthread_mutex_unlock(&mutex);
        sem_post(&sc_sem);
        int n=rand()%3;
        sleep(3);
    }
    

}

int main()
{
    pthread_mutex_init(&mutex,NULL);//初始化互斥锁
    sem_init(&sc_sem,0,BUFF_MAX);//初始化sc_sem信号量
    sem_init(&xf_sem,0,BUFF_MAX);//初始化xf_sem信号量

    srand((int)time(NULL));//随机种子

    pthread_t sc_id[2];//定义2个生产者线程
    for(int i=0;i<2;i++)//创建并启动这两个生产者线程
    {
        pthread_create(&sc_id[i],NULL,sc_fun,NULL);
    }

    pthread_t xf_id[3];//定义3个消费者线程
    for(int i=0;i<3;i++)
    {
        pthread_create(&xf_id[i],NULL,xf_fun,NULL);
    }

    for(int i=0;i<2;i++)
    {
        pthread_join(sc_id[i],NULL);//等待2个生产者线程结束
    }
    for(int i=0;i<3;i++)
    {
        pthread_join(xf_id[i],NULL);//等待3个消费者线程结束
    }

    sem_destroy(&sc_sem);//销毁生产者信号量
    sem_destroy(&xf_sem);//销毁消费者信号量

    pthread_mutex_destroy(&mutex);//销毁锁

    exit(0);

}

部分运行结果如下:

在这里插入图片描述

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

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

相关文章

Spring boot(一)

Spring Boot是一个构建在Spring框架顶部的项目。它提供了一种简便&#xff0c;快捷的方式来设置&#xff0c;配置和运行基于Web的简单应用程序。 它是一个Spring模块&#xff0c;提供了 RAD(快速应用程序开发)功能。它用于创建独立的基于Spring的应用程序&#xff0c;因为它需…

SAP‘s ECC6 EoL(End of Life) 支持服务声明 2027?

前言 一、EoL公告信息&#xff0c;2027&#xff1f; 二、继续使用ECC6.0的选项 1.引入第三方支持 2.S/4 HANA 3.SAP Business ByDesign 4.SAP Business One 总结 最新的公告是&#xff1a;2027年&#xff0c;SAP ECC 6.0将停止得到支持&#xff0c;并退出主流SAP支持&am…

组装电脑及问题排查,成功点亮

组件购买 模块描述渠道价格CPUi5 13600KF 盒装拼多多&#xffe5;1918散热器风冷利民PA120京东&#xffe5;197主板华硕ROG 吹雪 B70G京东&#xffe5;1479内存条金士顿DDR5 16G * 2京东&#xffe5;699固态硬盘致态 Tipro 7000 2t京东&#xffe5;940显卡七彩虹4060京东&…

(AcWing) 数字三角形

给定一个如下图所示的数字三角形&#xff0c;从顶部出发&#xff0c;在每一结点可以选择移动至其左下方的结点或移动至其右下方的结点&#xff0c;一直走到底层&#xff0c;要求找出一条路径&#xff0c;使路径上的数字的和最大。 73 88 1 02 7 4 4 4 5 2 6 …

在向excel写入长数据用TEXT不能写完解决办法

在编程时&#xff0c;程序向excel表格写入很长的内容时&#xff0c;发现表格不能够完全显示出来&#xff0c;原因是TEXT类型的长度限制&#xff0c;如果强制加上长度&#xff0c;比如TEXT(1000)就会报错&#xff0c;提示错误信息大小过长&#xff0c;这时候需要换一个格式:LONG…

【初识篇】ESP-IDF零基础入门 4 —— 编译工具讲解

系列文章目录 【文章导航】基于 ESP-IDF 框架的 ESP32 零基础入门系列教程 文章目录 系列文章目录前言1. idf.py 简介2. idf.py 用法3. idf.py 常用命令4. 使用 idf.py 命令编译项目的流程 前言 上一篇教程里&#xff0c;我们学习了如何创建自己的工程&#xff0c;那么现在我…

当高并发来袭:StarRocks Query Cache 一招搞定!

您是否曾经遇到这样的情况&#xff1f;每天早上或业务活动高峰期&#xff0c;大量用户涌入报表平台或数据应用&#xff0c;希望查看特定业务领域的最新指标或趋势。这些用户可能会基于庞大的数据集进行大量类似的聚合查询&#xff0c;造成集群的 CPU 负载持续攀升&#xff0c;从…

基于随机森林的乳腺癌诊断

在当今的现实生活中存在着很多种微信息量的数据,如何采集这些数据中的信息并进行利用,成了数据分析领域里一个新的研究热点。随机森林以它自身固有的特点和优良的分类效果在众多的机器学习算法中脱颖而出。 随机森林算法由Leo Breiman和 Adele Cutler提出,该算法结合了…

JMeter使用方法

一、基础简介 界面 打开方式 双击 jmeter.bat双击 ApacheJMeter.jsr命令行输入 java -jar ApacheJMeter.jar 目录 BIN 目录&#xff1a;存放可执行文件和配置文件 docs目录&#xff1a;api文档&#xff0c;用于开发扩展组件 printable-docs目录&#xff1a;用户帮助手册 li…

海思Hi3861L开发一-环境搭建

一、简介 之前的文章中有详细介绍了HarmonyOS的Hi3861开发,但是该开发是基于HarmonyOS来的。实际在项目开发中,可能不会用到HarmonyOS,用的还是原生的Hi3861。那这次就重新学习Hi3861L。 二、环境搭建 环境:Ubuntu18.04.5 关于Ubuntu的环境搭建,还是参考之前的文章,附上…

musl libc ldso 动态加载研究笔记:动态库的加载次序与初始化次序

前言 musl ldso 是按照什么次序加载动态链接的应用程序的共享库的&#xff1f;如果共享库之间有依赖&#xff0c; musl ldso 如何处理先初始化哪个 共享库&#xff1f; musl ldso 的代码可以在 musl 官方代码&#xff1a; ldso\dlstart.c 与 ldso\dynlink.c&#xff0c;其中动…

table,设置 数据相同时, 合并列

<el-table :data"tableData" :span-method"objectSpanMethod" border style"width: 100%" show-summary><el-table-column type"index" label"序号" width"100" /><el-table-column prop"dat…

c++(8.23)类,this指针,构造函数,析构函数,拷贝构造函数

设计一个Per类&#xff0c;类中包含私有成员&#xff1a;姓名、年龄、指针成员身高、体重&#xff0c;再设计一个Stu类&#xff0c;类中包含私有成员&#xff1a;成绩、Per类对象 p1&#xff0c;设计这两个类的构造函数、析构函数和拷贝构造函数。 #include <iostream>u…

idea的断点调试

1、行断点 首先在代码的最左侧点击会显示红色的圆圈 第二步在main方法中右键选中debug run进行运行 会出现下面图片的情况 出现上图之后&#xff0c;点击console 下一步 这个时候就可以看到调试的结果了 6、方法调用栈&#xff1a;这里显示了该线程调试所经过的所有方法&…

SQL注入之堆叠查询

文章目录 堆叠查询是什么&#xff1f;堆叠查询修改所有用户密码堆叠查询删除数据库恢复数据库 堆叠查询是什么&#xff1f; 在SQL中&#xff0c;分号;是用来表示一条sql语句的结束。试想一下我们在; 结束一个sql语句后继续构造下一条语句&#xff0c;会不会一起执行&#xff1f…

漏洞挖掘和安全审计的技巧与策略

文章目录 漏洞挖掘&#xff1a;发现隐藏的弱点1. 源代码审计&#xff1a;2. 黑盒测试&#xff1a;3. 静态分析工具&#xff1a; 安全审计&#xff1a;系统的全面评估1. 渗透测试&#xff1a;2. 代码审计&#xff1a;3. 安全策略审查&#xff1a; 代码示例&#xff1a;SQL注入漏…

VB.NET调用VB6 Activex EXE实现PowerBasic和FreeBasic的标准DLL调用

VB6写的ActiveX EXE公共对象是外置进程&#xff0c;因此&#xff0c;尽管它是x86 32位的进程&#xff0c;但可以集成到 VB.NET的x64和x32程序中使用。 VS2022的VB.NET程序&#xff0c;调用ActiveX DLL对象我在上篇笔记中写了 VB.NET通过VB6 ActiveX DLL调用PowerBasic及FreeB…

【网络】DNS | ICMP | NAT | 代理服务器

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《网络》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; 前面几篇文章虽然讲介绍了整个网络通信的协议栈&#xff0c;我们也知道了完整的网络通信过程&#xff…

线性回归的正则化改进(岭回归、Lasso、弹性网络),最小二乘法和最大似然估计之间关系,正则化

目录 最小二乘法 极大似然估计的思想 概率&#xff1a;已知分布参数-对分布参数进行估计 概率描述的是结果;似然描述的是假设/模型​编辑 似然&#xff1a;已知观测结果-对分布参数进行估计​编辑 对数函数消灭连乘-连乘导致算法参数消失 极大似然估计公式&#xff1a;将乘…

Android BatteryManager的使用及BatteryService源码分析

当需要监控系统电量时&#xff0c;用 BatteryManager 来实现。 参考官网 监控电池电量和充电状态 获取电池信息 通过监听 Intent.ACTION_BATTERY_CHANGED 广播实现&#xff0c;在广播接收器中获取电池信息。 这是个粘性广播&#xff0c;即使过了广播发出的时间点后再注册广…