当前位置: 首页 > 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.xdnf.cn/news/1100395.html

相关文章:

  • 人事系统选型与应用全攻略:从痛点解决到效率跃升的实战指南
  • 区块链应用场景深度解读:金融领域的革新与突破
  • 资源分享-FPS, 矩阵, 骨骼, 绘制, 自瞄, U3D, UE4逆向辅助实战视频教程
  • 将Blender、Three.js与Cesium集成构建物联网3D可视化系统
  • 【SpringAI】6.向量检索(redis)
  • javaweb之相关jar包和前端包下载。
  • PHY模式,slave master怎么区分
  • 7.11文件和异常
  • 什么是进程、什么是线程(进程、线程的全方面解析)
  • 界面组件DevExpress WPF中文教程:Grid - 如何检查节点?
  • 在 React Three Fiber 中实现 3D 模型点击扩散波效果
  • JavaWeb笔记二
  • 企业级配置:Azure 邮件与 Cloudflare 域名解析的安全验证落地详解
  • CReFT-CAD 笔记 带标注工程图dxf,png数据集
  • JVM 内存结构
  • 每天一个前端小知识 Day 29 - WebGL / WebGPU 数据可视化引擎设计与实践
  • 人工智能-基础篇-29-什么是低代码平台?
  • AI问答之手机相机专业拍照模式的主要几个参数解释
  • 人工智能-基础篇-28-模型上下文协议--MCP请求示例(JSON格式,客户端代码,服务端代码等示例)
  • 大数据学习7:Azkaban调度器
  • 《Effective Python》第十三章 测试与调试——使用 Mock 测试具有复杂依赖的代码
  • Three.js+Shader实现三维波动粒子幕特效
  • 量子计算系统软件:让“脆弱”的量子计算机真正可用
  • DDL期间TDSQL异常会话查询造成数据库主备切换
  • 【NLP入门系列六】Word2Vec模型简介,与以《人民的名义》小说原文实践
  • 如何利用个人电脑搭建数据库服务器实现远程协作
  • RabbitMQ用法的6种核心模式全面解析
  • 零基础入门物联网-远程门禁开关:云平台创建
  • 自动驾驶控制系统
  • 李宏毅(深度学习)--(2)