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

Linux线程 --- 生产者消费者模型(C语言)

在学习完线程相关的概念之后,本节来认识一下Linux多线程相关的一个重要模型----“ 生产者消费者模型”

本文参考:

Linux多线程生产者与消费者_红娃子的博客-CSDN博客

Linux多线程——生产者消费者模型_linux多线程生产者与消费者_两片空白的博客-CSDN博客

数据结构“入门”—队列(C语言实现)_队列c语言_Fan~Fan的博客-CSDN博客 

生产者消费者模式保姆级教程 (阻塞队列解除耦合性) 一文帮你从C语言版本到C++ 版本, 从理论到实现 (一文足以)_阻塞队列实现生产者消费者模式_小杰312的博客-CSDN博客

生产者与消费者的概念

这个模型的答题逻辑可以使用信号量(POSIX信号量)互斥量+条件变量 实现,这里介绍使用互斥量+条件变量的方法。

 一个进程中的线程有两种角色,一种是生产者,一种是消费者。生产者为消费者提供任务,消费者拿到任务,解决任务。

在生成者和消费者之间还有一个"交易场所",是一个内存块。生成者线程将任务放到内存块中,消费者线程在内存块中拿任务。当内存块数据达到一高水位线时,生产者会进行等待,唤醒消费者拿任务,当内存块数据达到一低水位线时,消费者会等待,并且唤醒生产者生产任务。(条件变量)通过这个模型,可以解除生产者和消费者的强耦合问题。

生成者,消费者存在着3种关系。生产者和生产者之间是互斥的关系消费者和消费者之间是互斥的关系生产者和消费者之间是互斥和同步的关系

对于生产者:

对于消费者:

 

关键的问题,在于生产者和消费者什么时候睡眠,又什么时候被唤醒从哪里读取和写入,这就是生产者和消费者模型的关键

什么时候睡眠和唤醒在上图已经演示,从哪里读取和写入的答案应该是“队列”。

所以生产者和消费者不直接相互通信,而是通过队列,队列就是这个模型可以解耦的关键。

C语言的队列 

既然要学习队列,就要先学习C语言的队列相关知识:

队列的概念

只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出(FIFO)的属性。入队是从队尾添加数据,出队是从队头读取数据。

队列的实现 

队列的实现可以使用数组或者是链表结构,相对而言链表的结构更优一些。

 

使用阻塞队列来实现生产者消费者的模型

在多线程编程中,阻塞队列是一种常用于实现生产者和消费者模型的数据结构。其普通队列区别在于,当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放入元素当队列满的时候,往队列中存放元素的操作也会被阻塞,直到有元素从队列中取出

队列的形式并不重要,这里采用环形队列:

实现思路

生产者和生产者之间互斥,消费者和消费者之间互斥

  • 在生产和消费的时候需要定义两个互斥量,一个是生产者之间的,一个是消费者之间的。

生产者和消费者之间互斥且同步

  • 定义一个互斥量,取数据的时候,不能放,放数据的时候,不能取
  • 有两个条件,满和空,定义两个条件变量

代码展示(认真看注释!!

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#include <signal.h>//定义阻塞队列
typedef  struct BlockQueue { //使用了typedef给结构体起了一个别名“BlockQueue”size_t cap;//容量size_t size;//当前产品个数int front;//队头游标int tail;//队尾游标int* data; //数据域,创建了一个数据类型为int的数组,data指向数组头的地址pthread_mutex_t lock;pthread_cond_t full;pthread_cond_t free;
} BlockQueue;BlockQueue* bqp;//定义全局方便信号处理时候销毁 
//如果没有使用typedef,此处就应该为“struct BlockQueue *bqp;”
//此处定义指针的原因是,接下来有很多函数会修改结构体的参数,如果直接传结构体作为形参,那么修改的就是局部变量,只有传入指向结构体的指针才能方便的直接在函数内修改结构体参数void DestroyBlockQueue(BlockQueue* bqp) { //销毁队列free(bqp->data); //释放分配给指针的空间,防止资源无效占用pthread_mutex_destroy(&bqp->lock);pthread_cond_destroy(&bqp->full);pthread_cond_destroy(&bqp->free);
}void handler(int signo) { //自定义的信号处理函数,详见main的signal函数printf("ByeBye\n");DestroyBlockQueue(bqp);exit(EXIT_SUCCESS);
}BlockQueue* InitQueue(int n) { //初始化阻塞队列BlockQueue* bqp = (BlockQueue*)malloc(sizeof(BlockQueue)); //使用malloc分配空间bqp->cap = n; //容量为nbqp->size = 0; //当前产品个数为0bqp->front = bqp->tail = 0; //由于现在没有产品,所以队头和队尾的游标都是0bqp->data = (int*)malloc(sizeof(int) * n); //给指向数据的指针分配空间,大小是“容量” 乘以 “int型变量大小”pthread_mutex_init(&bqp->lock, NULL); //初始化互斥量pthread_cond_init(&bqp->free, NULL); //初始化代表“队列为空”的条件变量pthread_cond_init(&bqp->full, NULL);//初始化代表“队列已满”的条件变量return bqp; //返回指向结构体的指针
}int IsEmpty(BlockQueue* bqp) {//判断阻塞队列是否为空的函数return bqp->size == 0; //返回值如果是1则代表容量是0,队列空;反之代表队列容量不为0,队列非空
}int IsFull(BlockQueue* bqp) {//判断阻塞队列是否已满的函数return bqp->size == bqp->cap; //返回值如果是1则代表当前产品个数=容量,队列满;反之代表队列未满
}void WaitConsume(BlockQueue* bqp) {//消费被阻塞, 此时队列为空,等待队列有产品pthread_cond_wait(&bqp->full, &bqp->lock);
}void WaitProduct(BlockQueue* bqp) {//生产被阻塞, 此时队列已满,等待队列有空位pthread_cond_wait(&bqp->free, &bqp->lock);
}void NotifyConsume(BlockQueue* bqp) {//通知消费, 队列中有产品了pthread_cond_signal(&bqp->full);
}void NotifyProduct(BlockQueue* bqp) {//通知生产, 队列中有空位了pthread_cond_signal(&bqp->free);
}void Lock(BlockQueue* bqp) { //上锁pthread_mutex_lock(&bqp->lock);
}void Unlock(BlockQueue* bqp) { //解锁pthread_mutex_unlock(&bqp->lock);}void Push(BlockQueue* bqp, int val) { //向队列中增加数据的函数,即生产的函数Lock(bqp);//上锁while (IsFull(bqp)) { //当队列已满的时候,不断执行以下代码,直到队列有空位出现WaitProduct(bqp);//生产被阻塞, 此时队列已满,等待队列有空位NotifyConsume(bqp);//不断催促消费,这样才可以使得队列有空位从而跳出循环}bqp->data[bqp->tail++] = val;//在data数组的尾部增加一个元素,并把队尾游标加一bqp->tail %= bqp->cap;//bqp->tail =  bqp->tail % bqp->cap,如果队尾的游标大小没到容量大小就保持不变,超出则取余//目的就是让队尾游标数值在超出容量数值的时候归0重新覆盖写//Unlock(bqp);//解锁bqp->size += 1;//当前产品数量加一NotifyConsume(bqp);//有产品了通知消费Unlock(bqp);//解锁
}void Pop(BlockQueue* bqp, int* popval) { //从队列中取出数据的函数,即消费的函数Lock(bqp);//上锁while (IsEmpty(bqp)) { //当队列为空的时候,不断执行以下代码,直到队列不为空WaitConsume(bqp);//消费被阻塞, 此时队列为空,等待队列有产品NotifyProduct(bqp);//不断催促生产,这样才可以使得队列有产品(非空)从而跳出循环}*popval = bqp->data[bqp->front++];//从data数组的头部读取一个消息,并把队头游标加一bqp->front %= bqp->cap; //bqp->front =  bqp->front % bqp->cap,如果队头的游标大小没到容量大小就保持不变,超出则取余//目的就是让队头游标数值在超出容量数值的时候归0从头重新读//Unlock(bqp);//解锁bqp->size -= 1;//当前产品数量减一NotifyProduct(bqp);//有空位了通知生产Unlock(bqp);//解锁
}void* ConsumeRoutine(void* args) {//消费者线程执行函数,所有消费者共用这个函数BlockQueue* bqp = (BlockQueue*)args; //此时的线程参数是一个包装好的结构体,在代码头已定义int popval = 0;for ( ;; ) { //相当于一个while(1)Pop(bqp, &popval);//消费的函数,消费一个队头的数据printf("PopVal is %d, and has %ld Products\n", popval, bqp->size); //bqp结构体中的size成员的类型是size_t,在系统中对size_t的定义是无符号长整形,要用%ld表示sleep(rand() % 3);//rand() % 3代表随机取一个0~2的整数,即随机睡眠0~2秒,,随机数种子在main中定义}return (void*)0;
}void* ProductRoutine(void* args) {//生产者线程执行函数,所有生产者共用这个函数BlockQueue* bqp = (BlockQueue*)args;int pushval = 0;for ( ;;  ) { //相当于一个while(1)pushval = rand() % 1024;//准备放入队列的数据(产品), 是一个0~1023的随机整数,随机数种子在main中定义Push(bqp, pushval);//生产的函数,将一个产品塞入队尾(生产一个产品)printf("PushVal is %d, and has %ld Products\n", pushval, bqp->size); //bqp结构体中的size成员的类型是size_t,在系统中对size_t的定义是无符号长整形,要用%ld表示sleep(rand() % 3); //rand() % 3代表随机取一个0~2的整数,即随机睡眠0~2秒,,随机数种子在main中定义}return (void*)0;
}int main() {signal(SIGINT, handler);//当键盘输入“CTRL+C”时,触发SIGINT信号,跳转到自定义的handler函数(信号相关概念)srand((unsigned int)time(NULL)); //使用“(unsigned int)time(NULL)”作为生成随机数的种子bqp = InitQueue(30); //初始化并赋值给结构体bqp,设定容量为30pthread_t consume1, consume2, product1, product2; //定义并创建4个线程pthread_create(&product1, NULL, ProductRoutine, (void*)bqp);//2个生产者使用生产者共用函数作为启动函数pthread_create(&product2, NULL, ProductRoutine, (void*)bqp);pthread_create(&consume1, NULL, ConsumeRoutine, (void*)bqp);//2个消费者使用消费者共用函数作为启动函数pthread_create(&consume2, NULL, ConsumeRoutine, (void*)bqp);pthread_join(product1, NULL);//4个线程等待退出pthread_join(product2, NULL);pthread_join(consume1, NULL);pthread_join(consume2, NULL);return 0;
}

运行效果:

相关文章:

Linux线程 --- 生产者消费者模型(C语言)

在学习完线程相关的概念之后&#xff0c;本节来认识一下Linux多线程相关的一个重要模型----“ 生产者消费者模型” 本文参考&#xff1a; Linux多线程生产者与消费者_红娃子的博客-CSDN博客 Linux多线程——生产者消费者模型_linux多线程生产者与消费者_两片空白的博客-CSDN博客…...

Vue2向Vue3过度核心技术computed计算属性

目录 1 computed计算属性1.1 概念1.2 语法1.3 注意1.4.案例1.5.代码准备 2 computed计算属性 VS methods方法2.1 computed计算属性2.2 methods计算属性2.3 计算属性的优势2.4 总结 3 计算属性的完整写法 1 computed计算属性 1.1 概念 基于现有的数据&#xff0c;计算出来的新属…...

芯片行业震荡期,数字后端还可以入吗?

自去年开始&#xff0c;芯片行业仿佛进入了动荡期&#xff0c;经历了去年秋招和今年春招的小伙伴都知道&#xff0c;如今找工作有多难。 半导体行业人才缩减、各大厂裁员&#xff0c;在加上高校毕业生人数破千万&#xff0c;对于即将踏入IC这个行业的应届生来说&#xff0c;今…...

“精准时空”赋能制造业智能化发展

作者&#xff1a;邓中亮 高达动态厘米级的高精度定位服务&#xff0c;不仅是北斗卫星导航系统的一大独门绝技&#xff0c;其在产业化应用层面也已逐步向普适化、标配化演进&#xff0c;并延展出时空智能新兴产业。 5月17日&#xff0c;当长征三号乙运载火箭成功发射北斗系统的…...

Kotlin协程flow发送时间间隔debounce

Kotlin协程flow发送时间间隔debounce debounce的作用是让连续发射的数据之间间隔起来。典型的应用场景是搜索引擎里面的关键词输入&#xff0c;当用户输入字符时候&#xff0c;有时候&#xff0c;并不希望用户每输入任何一个单字就触发一次后台真正的查询&#xff0c;而是希望…...

ServiceManager接收APP的跨进程Binder通信流程分析

现在一起来分析Server端接收&#xff08;来自APP端&#xff09;Binder数据的整个过程&#xff0c;还是以ServiceManager这个Server为例进行分析,这是一个至下而上的分析过程。 在分析之前先思考ServiceManager是什么&#xff1f;它其实是一个独立的进程&#xff0c;由init解析i…...

Git问题:解决“ssh:connect to host github.com port 22: Connection timed out”

操作系统 Windows11 使用Git IDEA 连接方式&#xff1a;SSH 今天上传代码出现如下报错&#xff1a;ssh:connect to host github.com port 22: Connection timed out 再多尝试几次&#xff0c;依然是这样。 解决 最终发现两个解决方案&#xff1a;&#xff08;二选一&#xf…...

在Eclipse中创建javaweb工程

新建动态web工程 点击project或other之后&#xff0c;如何快速找到Dynamic Web Project 填写工程名等详细信息 也许会出现下面的对话框 项目结构图...

Pycharm链接远程mysql报错

Pycharm链接远程mysql配置及相应报错如下&#xff1a; 解决方法&#xff1a; 去服务器确认Mysql版本号&#xff1a; 我的Mysql为5.7.43&#xff0c;此时Pycharm mysql驱动为8.0版本&#xff0c;不匹配&#xff0c;所以需要根据实际的版本选择对应的驱动&#xff1b;选择对应的版…...

【硕士论文完美复现】【价格型需求响应】基于需求侧响应的配电网供电能力综合评估(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…...

Android Okhttp 源码浅析三

核心方法 getResponseWithInterceptorChain() internal fun getResponseWithInterceptorChain(): Response {// Build a full stack of interceptors.val interceptors mutableListOf<Interceptor>()interceptors client.interceptorsinterceptors RetryAndFollowUpI…...

一分钟学会用pygame制作棋盘背景

一分钟一个Pygame案例&#xff0c;这一集我们来学习一下如何生成一个视频中的棋盘背景效果&#xff0c;非常非常简单。 视频教程链接&#xff1a;https://www.bilibili.com/video/BV17G411d7Ah/ 当然我们这里是用来做页面的背景&#xff0c;你也可以拿来做别的效果&#xff0…...

Java --- 包装类

一、包装类 Java针对八种基本数据类型定义了相应的引用类型&#xff1a;包装类&#xff08;封装类&#xff09;。 二、基本数据类型与包装类的基本转换 public class WrapperTest {public static void main(String[] args) {//基本数据类型转换为包装类Boolean aBoolean new…...

[运维] wvp 28181安装部署全流程(ubuntu2204)

部署wvp 系统环境 系统版本&#xff1a;ubuntu2204 安装相关工具 sudo apt update ## 编译工具 sudo apt install git openjdk-11-jdk tar vim cmake gcc g libsrtp2-dev libssl-dev ## ffmepg sudo apt install ffmpeg编译zlm 编译zlm git clone https://gitee.com/xia-…...

vue实现富文本

效果图展示 一、安装依赖 npm install vue-quill-editor --save二、具体使用 html <template><!-- 富文本 --><quill-editorref"myQuillEditor"v-model"content":options"editorOption"blur"onEditorBlur($event)"…...

uniapp 开发微信小程序使用echart的dataZoom属性缩放功能不生效!bug记录!

在本项目中使用的是这个echart库 在项目中添加了dataZoom配置项但是不生效&#xff0c;突然想到微信小程序代码大小的限制&#xff0c;之前的echarts.js是定制的&#xff0c;有可能没有加dataZoom组件。故重新定制echarts.js。之前用的echarts版本是5.0.0&#xff0c;这次也是…...

用户端Web自动化测试_L4

目录&#xff1a; selenium多浏览器处理执行 javascript 脚本headless无头浏览器使用capability配置参数解析企业微信实战cypress测试框架介绍Playwright测试框架介绍 1.selenium多浏览器处理 多浏览器测试背景 用户使用的浏览器(firefox,chrome,IE 等)web 应用应该能在任何…...

CAPL - Panel和TestModule结合实现测试项可选

目录 一、定义脚本编号和脚本组编号 1、测试组定义 2、测试脚本编号定义...

机器学习,过拟合与欠拟合,正则化与交叉验证

目录 机器学习 过拟合与欠拟合 正则化与交叉验证 正则化 交叉验证 机器学习 的目的是使学到的模型不仅对已知数据而且对未知数据都能有很好的预测能力。 不同的机器学习方法会给出不同的模型。当损失函数给定时&#xff0c;基于损失函数的模型的训练误差&#xff08;tra…...

gradio使用transformer模块demo介绍1:Text Natural Language Processing

文章目录 文本生成 Text Generation自动完成 Autocomplete情感分析 Sentiment Analysis命名实体识别 Name Entity Recognition NER多语种翻译文本生成 Text Generation import gradio as gr from transformers import pipelinegenerator = pipeline(text-generation, model=&l…...

HTML 语义化

目录 HTML 语义化HTML5 新特性HTML 语义化的好处语义化标签的使用场景最佳实践 HTML 语义化 HTML5 新特性 标准答案&#xff1a; 语义化标签&#xff1a; <header>&#xff1a;页头<nav>&#xff1a;导航<main>&#xff1a;主要内容<article>&#x…...

中南大学无人机智能体的全面评估!BEDI:用于评估无人机上具身智能体的综合性基准测试

作者&#xff1a;Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位&#xff1a;中南大学地球科学与信息物理学院论文标题&#xff1a;BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接&#xff1a;https://arxiv.…...

Docker 运行 Kafka 带 SASL 认证教程

Docker 运行 Kafka 带 SASL 认证教程 Docker 运行 Kafka 带 SASL 认证教程一、说明二、环境准备三、编写 Docker Compose 和 jaas文件docker-compose.yml代码说明&#xff1a;server_jaas.conf 四、启动服务五、验证服务六、连接kafka服务七、总结 Docker 运行 Kafka 带 SASL 认…...

【Java_EE】Spring MVC

目录 Spring Web MVC ​编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 ​编辑参数重命名 RequestParam ​编辑​编辑传递集合 RequestParam 传递JSON数据 ​编辑RequestBody ​…...

NFT模式:数字资产确权与链游经济系统构建

NFT模式&#xff1a;数字资产确权与链游经济系统构建 ——从技术架构到可持续生态的范式革命 一、确权技术革新&#xff1a;构建可信数字资产基石 1. 区块链底层架构的进化 跨链互操作协议&#xff1a;基于LayerZero协议实现以太坊、Solana等公链资产互通&#xff0c;通过零知…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

Python 实现 Web 静态服务器(HTTP 协议)

目录 一、在本地启动 HTTP 服务器1. Windows 下安装 node.js1&#xff09;下载安装包2&#xff09;配置环境变量3&#xff09;安装镜像4&#xff09;node.js 的常用命令 2. 安装 http-server 服务3. 使用 http-server 开启服务1&#xff09;使用 http-server2&#xff09;详解 …...

Oracle11g安装包

Oracle 11g安装包 适用于windows系统&#xff0c;64位 下载路径 oracle 11g 安装包...

【SpringBoot自动化部署】

SpringBoot自动化部署方法 使用Jenkins进行持续集成与部署 Jenkins是最常用的自动化部署工具之一&#xff0c;能够实现代码拉取、构建、测试和部署的全流程自动化。 配置Jenkins任务时&#xff0c;需要添加Git仓库地址和凭证&#xff0c;设置构建触发器&#xff08;如GitHub…...

自然语言处理——文本分类

文本分类 传统机器学习方法文本表示向量空间模型 特征选择文档频率互信息信息增益&#xff08;IG&#xff09; 分类器设计贝叶斯理论&#xff1a;线性判别函数 文本分类性能评估P-R曲线ROC曲线 将文本文档或句子分类为预定义的类或类别&#xff0c; 有单标签多类别文本分类和多…...