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

Linux:多线程---同步生产者消费者模型

文章目录

      • 1. 同步
        • 1.1 同步与互斥的关系
        • 1.2 条件变量
        • 1.3 条件变量的接口
        • 1.4 代码中易出问题的地方
        • 1.5 条件变量的使用
      • 2. 生产者消费者模型
        • 2.1 生产者消费者模型的概念

  • 序:在上一章中,我们深入了解了互斥的概念,浅谈了同步的概念,知道了线程安全的概念遗迹死锁及死锁的预防,而本章,我将深入同步,并带大家去深入生产者消费者模型。

1. 同步

1.1 同步与互斥的关系

调度问题和竞争问题导致的饥饿问题,就要用同步来处理,互斥是一种对于临界资源访问的保护,是一种解决方案,但是互斥只能在特定情况下才能发挥很好的作用,互斥具有局部性,而同步的方法,就能很好的填补这个空缺,互斥与同步不是两个对立的东西,而是互补的解决问题的方案。所以,同步问题是保证数据安全的情况下(互斥),让我们的线程访问资源具有一定的顺序性(同步)

1.2 条件变量

已经知道了什么是同步后,我们又该如何实现同步呢?—条件变量

还是以vip自习室为例:
在这里插入图片描述
对于这个自习室,每个同学来的时候一定是先来申请钥匙(锁资源)的,如果此时钥匙还没有人申请,则立马就能进入vip自习室进行自习,反之则进入等待队列的末尾中进行排队等待,而拿到钥匙的同学进入vip自习室出来后,需要先将钥匙释放后,敲响铃铛,铃铛响后,等待队列中的第一个队首的人就会离开等待队列,去申请钥匙,进入自习室。

其中的铃铛和等待队列就叫做条件变量,其中条件变量要包含简单的通知机制(铃铛),还要提供一个等待队列,能够让所有线程在等待队列中排列。

我们的库可以申请很多锁资源和条件变量,那么多锁资源就绪了,怎么知道是申请哪个锁资源,那么多条件变量就绪了,怎么知道是去哪个条件变量,所以,条件变量和锁都需要被管理,而要管理就离不开先描述,再组织,所以锁其实是一个结构体,方便管理,那么条件变量也一定是一个结构体,所以,对条件变量的管理就是对库中的结构体的管理。又因为能进入条件变量的等待队列进行等待的线程必定是先申请锁失败后,才进入等待队列进行等待的,所以条件变量就一定离不开互斥锁,或者说条件变量一定要配合互斥锁来使用。

总结:条件变量必须依赖于锁使用,让线程运行完后,进入一个等待队列中,由这个条件变量决定给谁锁(按照队列的先后顺序),依次执行。

1.3 条件变量的接口

在这里插入图片描述

pthread_cond_t:原生线程库提供的一个数据类型
pthread_cond_init:初始化一个条件变量,第一个参数cond填入要初始化的条件变量,第二个参数就是要填入的条件变量的属性,一般直接置为nullptr
pthread_cond_destroy:释放一个条件变量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER就是定义一个全局的条件变量,和锁的定差不多一样的,不需要显式调用pthread_cond_destroy函数进行释放

怎么把线程放入等待队列进行等待?当然也是调用接口

在这里插入图片描述

pthread_cond_wait:第一个参数传对应的条件变量,第二个参数传入对应的互斥锁

等待唤醒:

pthread_cond_broadcast函数:唤醒等待队列中的所有线程
pthread_cond_signal函数:唤醒队列中的第一个线程(唤醒一个线程)

1.4 代码中易出问题的地方

代码一如下:

#include<iostream>
#include<unistd.h>
#include<pthread.h>#define NUM 5void* handler(void *args)
{pthread_detach(pthread_self());uint64_t threadname = (uint64_t)args;std::cout<<"pthread:"<<threadname<<std::endl;
}int main()
{for(uint64_t i=0;i<NUM;i++){pthread_t tid;pthread_create(&tid,nullptr,handler,(void*)i);}while(true) sleep(1);return 0;
}

代码一结果如图:

在这里插入图片描述

代码二如下:

#include<iostream>
#include<unistd.h>
#include<pthread.h>#define NUM 5void* handler(void *args)
{pthread_detach(pthread_self());uint64_t threadname = *(uint64_t*)args;std::cout<<"pthread:"<<threadname<<std::endl;
}int main()
{for(uint64_t i=0;i<NUM;i++){pthread_t tid;pthread_create(&tid,nullptr,handler,(void*)&i);}while(true) sleep(1);return 0;
}

代码二结果如图:

在这里插入图片描述

观察代码一和代码二我们发现两个现象:

第一个是出现了相同的线程名,为什么第一个代码出现了,这是因为我们传i时候传的时候传的是i地址,而非i的参数,因为由于传的是i地址,所以当i发现改变,本质就是这个地址上的值发生了改变,从而导致线程的函数将改变后的i给赋了值,就会导致这样的情况,所以直接传地址就会导致这种数据上的错误,要注意容易出问题的地方。
第二个是打印在显示器上的内容错乱,这是因为打印在显示器上是在往显示器的文件中打印数据,而文件也是一种临界资源(共享资源),如果临界资源没有加锁(保护),就会出现同时访问,就容易出现打印在显示器上的信息是错乱的

1.5 条件变量的使用

演示代码如下:

#include<iostream>
#include<unistd.h>
#include<pthread.h>int cnt = 0;#define NUM 5pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;void* handler(void *args)
{pthread_detach(pthread_self());uint64_t threadname = (uint64_t)args;std::cout<<"pthread:"<<threadname<<",create success!"<<std::endl;while(true){pthread_mutex_lock(&mutex);pthread_cond_wait(&cond,&mutex); //? 为什么在这里?1.pthread_cond_wait让线程等待的时候,会自动释放锁std::cout<<"pthread:"<<threadname<<", cnt:"<<cnt++<<std::endl;pthread_mutex_unlock(&mutex);sleep(3);}
}int main()
{for(uint64_t i=0;i<NUM;i++){pthread_t tid;pthread_create(&tid,nullptr,handler,(void*)i);}sleep(3);std::cout<<"main thread ctrl begin: "<<std::endl;while(true){//唤醒在指定条件变量下的排队的线程pthread_cond_signal(&cond);std::cout<< "signal one thread..."<<std::endl;sleep(1);}return 0;
}

结果如图:
在这里插入图片描述

我们怎么知道我们要让哪一个线程去休眠?一定是临界资源不就绪,没错,临界资源也是有状态的!!!
你怎么知道临界资源是就绪还是不就绪?我们进行判断时,判断出来的!!!而判断临界资源也是访问临界资源,所以判断必须在加锁之后。

2. 生产者消费者模型

2.1 生产者消费者模型的概念

在这里插入图片描述

为什么有了超市,效率就高?假设没有超市,消费者就要找生产者拿所需的资源,而这也需要耗时,生产者生产也需要资源,导致消费者直接找生产者拿资源会很麻烦,但是如果生产者和消费者之间加了一个超市,就高效了呢?这是因为,生产者生产完资源后可以在超市中进行存储,消费者要资源的时候,就可以直接提供,不需要等了,这也符合为什么输入设备和输出设备之间为什么需要一个内存,一个道理。(超市—>大号的缓存)

厂家(生产者)关心的是超市(缓存)还有多少空位置,需要补多少货(资源),而顾客(消费者)关心的是超市(缓存)还有多少商品(资源),够不够买。生产和消费的行为,进行了一定程度上的解耦,生产者不需要考虑消费者,消费者不需要考虑生产者。

其中的生产者和消费者由线程承担,而超市就是特定结构的内存空间,而资源就是数据。本质是执行流在做通信。

其中超市可以支持忙闲不均,他是一个特定结构的内存空间(共享资源)—>会有并发问题

三种关系:

  1. 生产者vs生产者:互斥
  2. 消费者vs消费者:互斥
  3. 生产者vs消费者:互斥(保证数据安全),同步(生产和消费)

生产者消费者模型中:有3种关系,2种角色 — 生产和消费,1个交易场所 —特定结构的内存空间

优点:

  1. 支持忙闲不均
  2. 生产和消费进行解耦

总结:

本章深入同步,引出条件变量的概念,详细探讨了条件变量是什么,为什么和怎么用的三个重要问题,之后又对生产者消费者模型进行了一个大概的讲解。

http://www.lryc.cn/news/584848.html

相关文章:

  • 【Linux仓库】命令行参数与环境变量【进程·伍】
  • 【前端】【Echarts】【zrender】从入门到多路径信号流动动画实战
  • 飞算JavaAI:革新Java开发的智能助手
  • Linux kernel devm_gpiod_get()函数详解
  • 彻底解决JavaFx在Linux中文无法正常显示的问题(究其根本原因)
  • 飞书CEO谢欣:挑战巨头,打造AI新时代的Office
  • 锁的艺术:从Mutex到ReentrantLock,掌握并发编程的脉搏
  • 分布式系统高可用性设计-负载均衡与容错机制深度解析
  • Shader面试题100道之(81-100)
  • 模拟实现unordered_map
  • 如何使用 Python 删除 Excel 中的行、列和单元格 – 详解
  • 如何从0开始构建自己的第一个AI应用?(Prompt工程、Agent自定义、Tuning)
  • 格密码--数学基础--02基变换、幺模矩阵与 Hermite 标准形
  • AI金融风控:识别欺诈,量化风险的新利器
  • pandas销售数据分析
  • python 在 Linux CentOS 上安装 playwright 的完整步骤
  • Pandas:常见的转换函数(rename,set_index,reset_index)
  • MD2Doc转换器(基于Python)
  • [Reverse1] Tales of the Arrow
  • 飞算 JavaAI 深度体验:开启 Java 开发智能化新纪元
  • 闲庭信步使用图像验证平台加速FPGA的开发:第八课——图像数据的行缓存
  • Locust 负载测试工具使用教程
  • 为什么选择Selenium自动化测试?
  • [特殊字符]远程服务器配置pytorch环境
  • ajax和XMLHttpRequest以及fetch
  • STM32-DAC数模转换
  • day21——特殊文件:XML、Properties、以及日志框架
  • C#元组:从基础到实战的全方位解析
  • 实现在线预览pdf功能,后台下载PDF
  • 使用gdal读取shp及filegdb文件