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

news2024/11/19 3:17:36

在学习完线程相关的概念之后,本节来认识一下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; //容量为n
	bqp->size = 0; //当前产品个数为0
	bqp->front = bqp->tail = 0; //由于现在没有产品,所以队头和队尾的游标都是0
	bqp->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,设定容量为30
 
	pthread_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;
}

运行效果:

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

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

相关文章

测试平台metersphere

metersphere可以做接口测试、UI测试、性能测试。 metersphere接口测试底层是jmeter&#xff0c;可以做API管理&#xff0c;快捷调试&#xff0c;接口用例管理&#xff0c;接口自动化场景执行一键选取用例范围&#xff0c;生成测试报告。 会用jmeter&#xff0c;metersphere会…

软年架构复用-架构师之路(十一)

软件架构复用 软件产品线是 一组产业密集型系统&#xff0c;规定用公用的 核心资产集成 开发而来。 机会复用 和 系统复用。 机会复用&#xff1a;临时发现有可服用资产立马复用。 系统复用&#xff1a;开发之前进行规划好哪些需要复用。 复用的三个阶段&#xff1a; 获取到…

高阶数据结构并查集

目录&#xff1a; 并查集的概念代码实现 并查集的概念 将n个不同的元素划分成一些不相交的集合。开始时&#xff0c;每个元素自成一个单元元素集合&#xff0c;然后按一定的规律将归于同一组元素的集合合并。在此过程中反复遇到查询某一个元素属于那个集合的运算&#xff0c;这…

储能运行约束的Matlab建模方法

最近一段时间有很多人问我最优潮流计算中储能系统的建模方法。部分朋友的问题我回复了&#xff0c;有些没有回消息的&#xff0c;我就不再一一回复了&#xff0c;在这里我写一篇博客统一介绍一下。 1.储能系统介绍 首先&#xff0c;让【GPT】简单介绍一下储能系统&#xff1a;…

【多天线传输技术】BPSK调制信号在AWGN信道下的理论误码率与仿真误码率

%% [0、预处理] clc; clear; close all&#xff1b;%% [1、配置参数] N1000000; %数据点数&#xff08;个&#xff09; SNR_dB0:10; %信噪比&#xff08;dB形式&#xff09; SNR10.^(SNR_dB/10); %信噪比&#xff08;一般形式&#xff0c;Eb/N0&#xff09;…

【业务功能篇78】微服务-前端后端校验- 统一异常处理-JSR-303-validation注解

5. 前端校验 我们在前端提交的表单数据&#xff0c;我们也是需要对提交的数据做相关的校验的 Form 组件提供了表单验证的功能&#xff0c;只需要通过 rules 属性传入约定的验证规则&#xff0c;并将 Form-Item 的 prop 属性设置为需校验的字段名即可 校验的页面效果 前端数据…

Android相机-HAL子系统

引言 应用框架要通过拍照预览摄像获得照片或者视频,就需要向相机子系统发出请求, 一个请求对应一组结果 一次可发起多个请求&#xff0c;并且提交请求是非阻塞的&#xff0c;始终按照接收的顺序以队列的形式先进先出地进行顺序处理 一个请求包含了拍摄和拍照配置的所有信息&…

企业数字化转型中,VR数字展厅能有哪些体验?

在数字化转型的浪潮下&#xff0c;企业纷纷开始注重数字展厅的开展&#xff0c;VR虚拟展厅结合VR全景技术&#xff0c;可以创造出许多有趣的玩法和体验&#xff0c;无论是虚拟参观、互动体验还是VR云会议对接&#xff0c;都为企业客户带来了全新的感知方式。 同传统展厅相比&am…

【LeetCode-中等题】560. 和为 K 的子数组

题目 题解一&#xff1a;逆序枚举数组 //方法一:枚举数组&#xff08;顺序&#xff09;int count 0;// 记录最终符合条件的数组个数int n nums.length;for(int end 0; end<n ; end){int sum 0;//记录每一次经过的元素总和for(int start end; start>0;start--){sum n…

漏洞挖掘和漏洞利用技术:讨论漏洞发现、利用和修复,深入研究不同类型漏洞的技术细节

章节一&#xff1a;引言 在当今数字化时代&#xff0c;计算机技术的迅猛发展为我们的生活带来了无数便利&#xff0c;然而也伴随着各种安全威胁。恶意黑客利用漏洞进行攻击已成为一种常见现象。本文将深入探讨漏洞挖掘和漏洞利用技术&#xff0c;以及如何修复这些漏洞&#xf…

微信小程序路由以及跳转页面传递参数

路由 在app.json的pages里面写 "pages/页面/页面" 直接保存pages直接生成非常方便 跳转页面 wx.navigateTo() 保留当前页面&#xff0c;跳转到应用内的某个非tabBar页面。 <text bindtap"daka">点击</text> daka:function () {wx.navigateTo…

第3篇:vscode搭建esp32 arduino开发环境

第1篇:Arduino与ESP32开发板的安装方法 第2篇:ESP32 helloword第一个程序示范点亮板载LED 1.下载vscode并安装 https://code.visualstudio.com/ 运行VSCodeUserSetup-x64-1.80.1.exe 2.点击扩展&#xff0c;搜索arduino,并点击安装 3.点击扩展设置&#xff0c;配置arduino…

java+springboot+mysql村务档案管理系统

项目介绍&#xff1a; 使用javaspringbootmysql开发的村务档案管理系统&#xff0c;系统包含超级管理员、工作人员角色&#xff0c;功能如下&#xff1a; 超级管理员&#xff1a;系统用户管理&#xff08;工作人员管理&#xff09;&#xff1b;公开资料&#xff1b;会议记录&…

PDF校对:追求文档的精准与完美

随着数字化时代的到来&#xff0c;PDF已经成为了多数机构和个人首选的文件格式&#xff0c;原因在于它的稳定性、跨平台特性以及统一的显示效果。但是&#xff0c;对于任何需要公开或正式发布的文档&#xff0c;确保其内容的准确性是至关重要的&#xff0c;这就是PDF校对显得尤…

IDEA创建Spring,Maven项目没有resources文件夹

有时新建Spring或Maven项目时&#xff0c;会出现目录中main下无resources文件夹的情况&#xff0c;来一起解决一下&#xff1a; FIles|Project Structure 在Modules模块找到对应路径&#xff0c;在main下创建resources&#xff0c;右键main&#xff0c;选择新文件夹 输入文件…

瞎扯之synchronized

我是胡说八道君&#xff0c;鉴别一个人基础打得牢不牢&#xff0c;有没有这个举一反三、将学过的知识串联起来的能力&#xff0c;教你一个简单的方法&#xff1a;关键词联想法 有兴趣的童鞋也可以跟着我的思路去画一张思维导图哦!加入你自己联想道德的部分更重要 eg: 给你一个…

金桥跨越相伴岁月 桂冠加冕爱意时光 GP芝柏表演绎浪漫七夕

两个多世纪以前&#xff0c;康士坦特芝勒德 (Constant Girard) 与玛莉亚柏雷戈 (Marie Perregaux) 喜结连理&#xff0c;两颗心灵在爱意中交织&#xff0c;二人将姓氏结合&#xff0c;创立“Girard-Perregaux”芝柏表&#xff0c;成为数百年来瑞士高级制表中仅有的以夫妻双人姓…

VoxWeekly|The Sandbox 生态周报|20230821

欢迎来到由 The Sandbox 发布的《VoxWeekly》。我们会在每周发布&#xff0c;对上一周 The Sandbox 生态系统所发生的事情进行总结。 如果你喜欢我们内容&#xff0c;欢迎与朋友和家人分享。请订阅我们的 Medium 、关注我们的 Twitter&#xff0c;并加入 Discord 社区&#xf…

idea 对JavaScript进行debug调试

文章目录 1.新增 JavaScript Debug 配置2.配置访问地址3.访问url. 打断点测试 前言 : 工作中接手别人的前端代码没有注释&#xff0c;看浏览器的network或者console切来切去&#xff0c;很麻烦&#xff0c;可以试试idea自带的javscript debug功能。 1.新增 JavaScript Debug 配…

基于水循环算法优化的BP神经网络(预测应用) - 附代码

基于水循环算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码 文章目录 基于水循环算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码1.数据介绍2.水循环优化BP神经网络2.1 BP神经网络参数设置2.2 水循环算法应用 4.测试结果&#xff1a;5.Matlab代…