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;
}

运行效果:

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

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

相关文章

pytestx重新定义接口框架设计

概览 脚手架&#xff1a; 目录&#xff1a; 用例代码&#xff1a; """ 测试登录到下单流程&#xff0c;需要先启动后端服务 """test_data {"查询SKU": {"skuName": "电子书"},"添加购物车": {"sk…

一篇文章带你彻底了解Java常用的设计模式

文章目录 前言1. 工厂模式使用示例代码优势 2. 单例模式说明使用示例代码优势 3. 原型模式使用示例代码优势 4. 适配器模式使用示例代码优势 5. 观察者模式使用示例代码优势 6. 策略模式使用示例代码优势 7. 装饰者模式使用示例代码优势 8. 模板方法模式使用示例代码优势 总结 …

网络学生用品商店系统设计与实现(论文+源码)_kaic

摘 要 随着互联网的发展&#xff0c;人们的生活发生了巨大的变化&#xff0c;给人们的生活、工作等方面带来了相当大的提高&#xff0c;电子化成为了节约成本、调高效率的代名词。电子商务是利用微电脑技术和网络通讯技术进行的商务活动&#xff0c;买卖双方通过网络所进行各…

Windows商店引入SUSE Linux Enterprise Server和openSUSE Leap

在上个月的Build 2017开发者大会上&#xff0c;微软宣布将SUSE&#xff0c;Ubuntu和Fedora引入Windows 商店&#xff0c;反应出微软对开放源码社区的更多承诺。 该公司去年以铂金会员身份加入Linux基金会。现在&#xff0c;微软针对内测者的Windows商店已经开始提供 部分Linux发…

_数字矩阵

题目&#xff1a;一个3阶的数字矩阵如下&#xff1a; 1 2 3 8 9 4 7 6 5 现在给定数字n(1<n≤20)&#xff0c;输出n阶数字矩阵。 思路&#xff1a; 放出一条好玩的贪吃蛇&#xff0c;按照右下左上的顺序吃蛋糕&#xff0c;一边吃蛋糕&#xff0c;一边拉数字&#xff1b…

提升团队效率!探索多款热门一站式团队协作工具

“常见的几种团队协作工具有&#xff1a;Zoho Projects、Slack、Microsoft Teams、Asana、Trello等。” 团队协作已经成为了企业、组织和个人工作的重要组成部分。为了提高工作效率和协同能力&#xff0c;各种团队协作工具应运而生。本文将介绍团队协作工具的功能以及常见的几种…

LeetCode——有效的括号

这里&#xff0c;我提供一种用栈来解决的方法&#xff1a; 思路&#xff1a;栈的结构是先进后出&#xff0c;这样我们就可以模拟栈结构了&#xff0c;如果是‘&#xff08;’、‘{’、‘[’任何一种&#xff0c;直接push进栈就可以了&#xff0c;如果是‘}’、‘&#xff09;’…

vue3将通用组件注册成全局组件

一、问题重现 我们用过vue的人都知道会有一个components文件夹用来存放我们的通用组件&#xff1a; 这里我的通用组件就有四个&#xff0c;但是有一些是使用评率比较高的&#xff0c;如果很多地方要使用我还得导入相同的组件&#xff0c;写的都是一样的代码&#xff1a; impo…

wireshark进行网络监听

一、实验目的&#xff1a; 1&#xff09;掌握使用CCProxy配置代理服务器&#xff1b; 2&#xff09;掌握使用wireshark抓取数据包&#xff1b; 3&#xff09;能够对数据包进行简单的分析。 二、预备知识&#xff1a; 包括监听模式、代理服务器、中间人攻击等知识点&#xf…

研磨设计模式day09原型模式

目录 场景 代码实现 有何问题 解决方案 代码改造 模式讲解 原型与new 原型实例与克隆出来的实例 浅度克隆和深度克隆 原型模式的优缺点 思考 何时选用&#xff1f; 相关模式 场景 代码实现 定义订单接口 package com.zsp.bike.day08原型模式;/*** 订单的接口*…

跨境出海:如何轻松应对多账号管理

在如今的跨境电商时代&#xff0c;成功经营一个线上店铺不再仅仅需要商品和服务&#xff0c;还需要精通广告投放、营销策略等多个领域。 然而&#xff0c;老练的电商从业者知道&#xff0c;如果不重视平台账号的管理方法&#xff0c;可能会导致店铺或营销账号被关联&#xff0…

Idea配置Remote Host

一、打开RemoteHost窗口 双击shift打开全局搜索 搜索Tools→Deployment→Browse Remote Host或 idea项目顶部Tools→Deployment→Browse Remote Host 二、添加服务 右侧边栏打开RemoteHost&#xff0c;点击三个点&#xff0c;起个名字&#xff0c;选择type为SFTP&#xff…