[Linux][多线程][四][线程同步][POSIX信号量][环形队列生产者消费者模型][线程池]

news2025/1/12 4:05:23

目录

  • 1.POSIX信号量
    • 1.基本概念
    • 2.为什么要有信号量? --> 提高效率
    • 3.信号量的PV操作
    • 4.PV操作必须是原子操作
    • 5.申请信号量失败被挂起等待
    • 6.理解信号量大致结构
  • 2.信号量函数
    • 1.初始化
    • 2.销毁
    • 3.等待信号量 -- 申请信号量 --> P()
    • 4.发布信号量 -- 释放信号量 --> V()
  • 3.基于环形队列的生产者消费者模型
    • 1.基本概念
    • 2.信号量如何保护环形队列?
    • 3.空间资源和数据资源
    • 4.blank_sem和data_sem的初始值设置
    • 5.生产者和消费者申请和释放资源
    • 6.需要遵守的两个原则
    • 7.思考问题
  • 4.线程池
    • 1.概念
    • 2.线程池的优点
    • 3.线程池的应用场景
    • 4.线程池实现


1.POSIX信号量

1.基本概念

  • 信号量本质是一个计数器,是描述临界资源中资源数目的计数器,信号量能够更细粒度的对临界资源进行管理
  • 每个执行流在进入临界区之前都应该先申请信号量,申请成功就有了操作特定的临界资源的权限,当操作完毕后就应该释放信号量
  • 信号量存在的价值
    • 进行同步与互斥
    • 更细粒度的临界资源的管理
  • 注意:
    • POSIX信号量和System V信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源的目的
    • 但POSIX信号量可以用于线程间同步

2.为什么要有信号量? --> 提高效率

  • 当仅用一个互斥锁对临界资源进行保护时,相当于将这块临界资源看作一个整体,同一时刻只允许一个执行流对这块临界资源进行访问
  • 如果将这块临界资源再分割为多个区域,当多个执行流需要访问临界资源时,这些执行流访问的是临界资源的不同区域
    • 那么可以让这些执行流同时访问临界资源的不同区域,此时不会出现数据不一致等问题

3.信号量的PV操作

  • P操作:
    • 将**申请信号量(count–)**称为P操作
    • 申请信号量的本质就是申请获得临界资源中某块资源的使用权限,当申请成功时临界资源中资源的数目应该减一
    • 因此P操作的本质就是让计数器减一
  • V操作:
    • 将**释放信号量(count++)**称为V操作
    • 释放信号量的本质就是归还临界资源中某块资源的使用权限,当释放成功时临界资源中资源的数目就应该加一
    • 因此V操作的本质就是让计数器加一

4.PV操作必须是原子操作

  • 多个执行流为了访问临界资源会竞争式的申请信号量
    • 因此信号量是会被多个执行流同时访问的
    • 也就是说信号量本质也是临界资源
  • 但信号量本质就是用于保护临界资源的,不可能再用信号量去保护信号量,所以信号量的PV操作必须是原子操作
  • 内存中变量的++、–操作并不是原子操作,因此信号量不可能只是简单的对一个全局变量进行++、–操作

5.申请信号量失败被挂起等待

  • 当执行流在申请信号量时,可能此时信号量的值为0,也就是说信号量描述的临界资源已经全部被申请了
    • 此时该执行流就应该在该信号量的等待队列当中进行等待,直到有信号量被释放时再被唤醒
  • 信号量的本质是计数器,但不意味着只有计数器,信号量还包括一个等待队列

6.理解信号量大致结构

请添加图片描述


2.信号量函数

1.初始化

  • *原型:int sem_init(sem_t sem, int pshared, unsigned int value);
  • 参数:
    • sem**:**需要初始化的信号量
    • pshared**:**传入0值表示线程间共享,传入非0值表示进程间共享
    • value**:**信号量的初始值 --> 计数器的初始值
  • **返回值:**成功返回0,失败返回-1

2.销毁

  • *原型:int sem_destroy(sem_t sem);
  • **参数:sem:**需要销毁的信号量
  • **返回值:**成功返回0,失败返回-1

3.等待信号量 – 申请信号量 --> P()

  • *原型:int sem_wait(sem_t sem);
  • **参数:sem:**需要等待的信号量
  • 返回值:
    • 成功返回0,信号量的值减一
    • 失败返回-1,信号量的值保持不变

4.发布信号量 – 释放信号量 --> V()

  • *原型:int sem_post(sem_t sem);
  • **参数:sem:**需要发布的信号量
  • 返回值:
    • 成功返回0,信号量的值加一
    • 失败返回-1,信号量的值保持不变

3.基于环形队列的生产者消费者模型

  • 基于阻塞队列的生产者与消费者模型存在一个很大的问题就是他们其实是在串行运行的,并没有并行运行,这就导致他们的效率不是很高,而使用环形队列则可以解决这个问题

1.基本概念

  • 环形队列采用数组模拟,用模运算来模拟环状特性
    请添加图片描述

2.信号量如何保护环形队列?

  • 在blank_sem和data_sem两个信号量的保护后,环形队列中不可能会出现数据不一致的问题
    • 只有当生产者和消费者指向同一个位置并访问时,才会导致数据不一致的问题,而此时生产者和消费者在对环形队列进行写入或读取数据时,只有两种情况会指向同一个位置:
      • 环形队列为空时
      • 环形队列为满时
    • 但是在这两种情况下,生产者和消费者不会同时对环形队列进行访问
      • 当环形队列为空的时,消费者一定不能进行消费,因为此时数据资源为0
      • 当环形队列为满的时,生产者一定不能进行生产,因为此时空间资源为0
  • 当环形队列为空和满时,已经通过信号量保证了生产者和消费者的串行化过程
    • 而除了这两种情况之外,生产者和消费者指向的都不是同一个位置,因此该环形队列当中不可能会出现数据不一致的问题
    • 并且大部分情况下生产者和消费者指向并不是同一个位置,因此大部分情况下该环形队列可以让生产者和消费者并发的执行

3.空间资源和数据资源

  • 生产者关注的是空间资源,消费者关注的是数据资源
    • 只要环形队列中有空间,生产者就可以进行生产
    • 只要环形队列中有数据,消费者就可以进行消费

4.blank_sem和data_sem的初始值设置

  • blank_sem的初始值应该设置为环形队列的容量,因为刚开始时环形队列当中全是空间
  • data_sem的初始值应该设置为0,因为刚开始时环形队列当中没有数据

5.生产者和消费者申请和释放资源

  • 生产者申请空间资源,释放数据资源
    • 对于生产者来说,生产者每次生产数据前都需要先申请blank_sem:
      • 如果blank_sem的值不为0,则信号量申请成功,此时生产者可以进行生产操作。
      • 如果blank_sem的值为0,则信号量申请失败,此时生产者需要在blank_sem的等待队列下进行阻塞等待,直到环形队列当中有新的空间后再被唤醒
    • 当生产者生产完数据后,应该释放data_sem
      • 虽然生产者在进行生产前是对blank_sem进行的P操作,但是当生产者生产完数据,应该对data_sem进行V操作而不是blank_sem
      • 生产者在生产数据前申请到的是blank位置,当生产者生产完数据后,该位置当中存储的是生产者生产的数据,在该数据被消费者消费之前,该位置不再是blank位置,而应该是data位置
      • 当生产者生产完数据后,意味着环形队列当中多了一个data位置,因此应该对data_sem进行V操作
  • 消费者申请数据资源,释放空间资源
    • 对于消费者来说,消费者每次消费数据前都需要先申请data_sem
      • 如果data_sem的值不为0,则信号量申请成功,此时消费者可以进行消费操作
      • 如果data_sem的值为0,则信号量申请失败,此时消费者需要在data_sem的等待队列下进行阻塞等待,直到环形队列当中有新的数据后再被唤醒
    • 当消费者消费完数据后,应该释放blank_sem
      • 虽然消费者在进行消费前是对data_sem进行的P操作,但是当消费者消费完数据,应该对blank_sem进行V操作而不是data_sem
      • 消费者在消费数据前申请到的是data位置,当消费者消费完数据后,该位置当中的数据已经被消费过了,再次被消费就没有意义了,为了让生产者后续可以在该位置生产新的数据,我们应该将该位置算作blank位置,而不是data位置
      • 当消费者消费完数据后,意味着环形队列当中多了一个blank位置,因此应该对blank_sem进行V操作
        请添加图片描述

6.需要遵守的两个原则

  • 生产者和消费者不能对同一个位置进行访问(互斥)
    • 如果生产者和消费者访问的是环形队列当中的同一个位置,那么此时生产者和消费者就相当于同时对这一块临界资源进行了访问,这当然是不允许的
    • 而如果生产者和消费者访问的是环形队列当中的不同位置,那么此时生产者和消费者是可以同时进行生产和消费的,此时不会出现数据不一致等问题
  • 无论是生产者还是消费者,都不应该将对方套一个圈以上(格子的数量有限)
    • 生产者从消费者的位置开始一直按顺时针方向进行生产,如果生产者生产的速度比消费者消费的速度快,那么当生产者绕着消费者生产了一圈数据后再次遇到消费者,此时生产者就不应该再继续生产了,因为再生产就会覆盖还未被消费者消费的数据
    • 同理,消费者从生产者的位置开始一直按顺时针方向进行消费,如果消费者消费的速度比生产者生产的速度快,那么当消费者绕着生产者消费了一圈数据后再次遇到生产者,此时消费者就不应该再继续消费了,因为再消费就会消费到缓冲区中保存的废弃数据

7.思考问题

  • 对于多生产者和多消费者来说,要保证他们各自之间要满足互斥,就必须加锁,那么这把锁是在信号量之前加还是之后加呢?
    • 首先明确一点,信号量也是原子性的
    • 在获取信号量之前进行加锁
      • 在这种情况下,也就意味着只有一个执行流能够竞争到锁,然后申请信号量
      • 那么对这个临界资源进行划分的意义何在呢,和之前的单生产者单消费者没有太大的区别,显然没有太大的价值
    • 在获取信号量之后进行加锁
      • 在这种情况下,当多个执行流访问临界资源的时候,他们都要去申请信号量,但是只会有一个执行流竞争锁成功
      • 等到这个执行流执行完毕后,下一个执行流就不需要再去申请信号量然后竞争锁,因为它是拿着信号量被挂起的
    • 总结:
      • 在获取信号量之后进行加锁,确保了每个执行流都能预定到相应的部分临界资源,相比第一种做法效率高一些
  • 多生产多消费的意义在哪里?
    • 不要狭隘的认为,把任务或者数据放在交易场所,就是生产和消费
    • 将数据或者任务生产前和拿到之后处理,才是最耗费时间的
    • 生产的本质:私有的任务 --> 公共空间中
    • 消费的本质:公共空间中的任务 --> 私有的
  • 信号量本质是一把计数器,计数器的意义是什么?
    • 在使用互斥量时
      • 申请锁 --> 判断与访问 --> 释放锁 --> 本质是并不清楚临界资源的情况
    • 信号量要提前预设资源的情况,而且在PV变化过程中,可以在外部就能知晓临界资源的情况
      • 可以不用进入临界区,就可以得知资源情况
      • 甚至可以减少临界区内部的判断

4.线程池

1.概念

  • 线程池是一种线程使用模式
  • 线程过多会带来调度开销,进而影响缓存局部和整体性能,而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务
  • **注意:**线程池中可用线程的数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量

2.线程池的优点

  • 线程池避免了在处理短时间任务时创建与销毁线程的代价
    • 线程池能够去掉创建线程的时间
  • 线程池不仅能够保证内核充分利用,还能防止过分调度
  • 防止服务器线程过多导致的系统过载问题
    • 如果任务来的时候才创建线程意味着,当大量任务同时到来,就需要创建大量线程,这时服务器就扛不住了挂掉了
    • 但是使用多线程的方式保证线程的个数是稳定的,就决定了服务器不会因为受到大量请求到来时而造成的冲击进而导致服务器宕机
  • 相对于进程池,线程池资源占用较少,但是健壮性很差

3.线程池的应用场景

  • 需要大量的线程来完成任务,且完成任务的时间比较短
    • 像Web服务器完成网页请求这样的任务,使用线程池技术是非常合适的
      • 因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数
    • 对于长时间的任务,比如Telnet连接请求,线程池的优点就不明显了
      • 因为Telnet会话时间比线程的创建时间大多了
  • 对性能要求苛刻的应用
    • 比如要求服务器迅速响应客户请求
  • 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用
    • 突发性大量客户请求,在没有线程池的情况下,将产生大量线程
    • 虽然理论上大部分操作系统线程数目最大值不是问题,但短时间内产生大量线程可能使内存到达极限,出现错误

4.线程池实现

请添加图片描述

  • 交互接口
    • 线程池中的多个线程负责从任务队列当中拿任务,并将拿到的任务进行处理
    • 线程池对外提供一个Push接口,用于让外部线程能够将任务Push到任务队列当中
  • 线程池中需要用到互斥锁和条件变量
    • 线程池中的任务队列是会被多个执行流同时访问的临界资源
      • 因此需要引入互斥锁对任务队列进行保护
    • 线程池当中的线程要从任务队列里拿任务,前提条件是任务队列中必须要有任务
      • 因此线程池当中的线程在拿任务之前,需要先判断任务队列当中是否有任务
      • 若此时任务队列为空,那么该线程应该进行等待,直到任务队列中有任务时再将其唤醒
      • 因此需要引入条件变量
    • 当外部线程向任务队列中Push一个任务后,此时可能有线程正处于等待状态,因此在新增任务后需要唤醒在条件变量下等待的线程
  • 注意:
    • 当某线程被唤醒时,其可能是被异常或是伪唤醒,或者是一些广播类的唤醒线程操作而导致所有线程被唤醒,使得在被唤醒的若干线程中,只有个别线程能拿到任务
      • 此时应该让被唤醒的线程再次判断是否满足被唤醒条件,所以在判断任务队列是否为空时,应该使用while进行判断,而不是if
    • 如果执行任务特别耗费时间,任务已经从任务队列里面拿出来了,线程自己私有就不在临界资源里了
      • 执行任务处理的代码不应该在临界区里面执行,可以让一个线程把任务拿出来之后先把锁释放掉,自己再处理消化任务
      • 好处是,它正在处理任务的同时其他线程正在拿任务,可以并行处理
  • 唤醒在条件变量下等待的线程用signal,而不是broadcast
    • 什么是惊群效应:
      • 惊群效应是指多进程(多线程)在同时阻塞等待同一个事件的时候(休眠状态)
      • 如果等待的这个事件发生,那么他就会唤醒等待的所有进程(或者线程)
      • 但是最终却只能有一个进程(线程)获得这个时间的"控制权",对该事件进行处理
      • 而其他进程(线程)获取"控制权"失败,只能重新进入休眠状态,这种现象和性能浪费就叫做惊群效应
    • 惊群效应消耗了什么:
      • Linux****内核对用户进程(线程)频繁地做无效的调度、上下文切换等使系统性能大打折扣
      • 上下文切换(context switch)过高会导致CPU像个搬运工,频繁地在寄存器和运行队列之间奔波,更多的时间花在了进程(线程)切换,而不是在真正工作的进程(线程)上面。
        • 直接的消耗包括CPU寄存器要保存和加载(例如程序计数器)、系统调度器的代码需要执行
        • 间接的消耗在于多核cache之间的共享数据
      • 为了确保只有一个进程(线程)得到资源,需要对资源操作进行加锁保护,加大了系统的开销
  • 为什么线程池中的线程执行历程需要设置为静态方法?
    • 使用pthread_create函数创建线程时,需要为创建的线程传入一个Routine(执行例程),该Routine只有一个为void的参数,以及返回类型为void的返回值。
    • 而此时Routine作为类的成员函数,该函数的第一个参数是隐藏的this指针
      • 因此这里的Routine函数,虽然看起来只有一个参数,而实际上它有两个参数
      • 此时直接将该Routine函数作为创建线程时的执行例程是不行的,无法通过编译。
    • 静态成员函数属于类,而不属于某个对象,没有隐藏的this指针的
      • 因此需要将Routine设置为静态方法,此时Routine函数才真正只有一个参数类型为void*的参数。
    • 静态成员函数内部无法调用非静态成员函数,但可能需要在Routine函数当中调用该类的某些非静态成员函数
      • 因此需要在创建线程时,向Routine函数传入当前对象的this指针
      • 此时就能够通过该this指针在Routine函数内部调用非静态成员函数了

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

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

相关文章

LeetCode 2739. 总行驶距离

题目链接https://leetcode.cn/problems/total-distance-traveled/?envTypedaily-question&envId2024-04-25 简单题,看代码思考一下即可理解 class Solution {public int distanceTraveled(int mainTank, int additionalTank) {int res 0;while (mainTank >…

探索直播+电商系统中台架构:连接消费者与商品的智能纽带

随着直播电商的崛起,电商行业进入了全新的智能时代。直播形式的互动性和即时性为消费者提供了全新的购物体验,而电商平台则为商品的展示、销售和配送提供了强大的支持。在这一背景下,直播电商系统中台架构成为了连接消费者与商品的智能纽带&a…

模块化 手写实现webpack

模块化 common.js 的导入导出方法: require \ export 和 module.exports export 和 module.export nodejs 内存1.4G -> 2.8G cjs ESModule 主要区别: require属于动态类型:加载执行 同步 esmodul是静态类型:引入时并不会真的去…

Oracle 21 C 安装详细操作手册,并配置客户端连接

Oracle 21 C 安装详细操作手册 Win 11 Oracle 21C 下载: Database Software Downloads | Oracle 中国 云盘共享 链接:https://pan.baidu.com/s/12XCilnFYyLFnSVoU_ShaSA 提取码:nfwc Oracle 21C 配置与登陆: 开始菜单 NetMa…

一线实战,一次底层超融合故障导致的Oracle异常恢复

背景概述 某客户数据由于底层超融合故障导致数据库产生有大量的坏块,最终导致数据库宕机,通过数据抢救,恢复了全部的数据。下面是详细的故障分析诊断过程,以及详细的解决方案描述: 故障现象 数据库宕机之后&#xff0c…

重要!!!涉及huggingface和kaggle的深度学习各种(文本图像视频音频)任务及其对应模型和案例代码总结

可以到hugging face官网,里面有对应的各种学习任务,数据集以及代码和预训练模型也可以到kaggle官网,里面有各种模型以及代码、数据集等。特色优势是:里面对应的数据集和模型都会有超过3个的代码,是用户发布的。 https…

如何批量跟踪京东物流信息

随着电商行业的快速发展,快递业务日益繁忙,无论是商家还是消费者,都需要一种高效、便捷的快递查询工具。快递批量查询高手软件应运而生,以其强大的功能和便捷的操作体验,赢得了广大电商、微商精英们的青睐。 快递批量…

4.25 C高级

思维导图 作业 2.输入两个数,实现两个数的排序 3.输入一个数,计算是否是水仙花 if ((g*g*gs*s*sb*b*bnum)) then echo YES else echo no fi 4.输入一个成绩实现登记判断 90-100A 80-89B 70-79C 60-69D 0-59E

第二证券|光通信概念拉升,吴通控股、胜蓝股份涨停,新易盛等大涨

光通信概念24日盘中强势拉升,截至发稿,吴通控股、胜蓝股份“20cm”涨停,新易盛涨超10%,四川九洲亦涨停,源杰科技、铭普光磁、立昂技能等涨超5%。 吴通控股昨日晚间披露的一季度报告显示,公司完成营业收入1…

CTF网络安全大赛详情

网络安全已成为现代社会的一个关键挑战,随着互联网技术的飞速发展,从个人隐私保护到国家安全,网络安全的重要性日益突显。为了应对这一挑战,CTF(Capture The Flag,中文:夺旗赛)应运而…

Clickhouse离线安装教程

https://blog.51cto.com/u_15060531/4174350 1. 前置 1.1 检查服务器架构 服务器:Centos7.X 需要确保是否x86_64处理器构架、Linux并且支持SSE 4.2指令集 grep -q sse4_2 /proc/cpuinfo && echo "SSE 4.2 supported" || echo "SSE 4.2 …

java-spring 06 图灵 getBean方法和 doGetBean方法

01.一般的流程是,这里是从上一章的preInstantiateSingleton方法顺序过来的。 getBean() -> doGetBean() -> createBean() -> doCreateBean() -> createBeanInstance() -> populateBean() -> initializeBean() 02.getBean方法,一般就…

网络安全中的加密与解密技术:全面指南及实验

引言 在当今数字时代,加密技术是保护数据安全的重要工具。从个人通讯到企业数据保护,加密帮助确保信息在存储和传输过程中的机密性和完整性。本文旨在全面介绍加密和解密的原理、常见算法以及实验,以帮助读者深入理解其在网络安全中的应用。…

自然资源调查监测评价系统:守护绿色地球的先锋

随着人类对自然资源的日益依赖,如何合理、可持续地利用这些资源成为了全球关注的焦点。自然资源调查监测评价系统,作为守护绿色地球的重要工具,正发挥着越来越重要的作用。本文将带您了解这一系统的内涵、功能及其在现代社会中的意义。一、自…

【SpringBoot】00 Maven配置及创建项目

一、Maven配置 1、下载Maven 进入官网下载:Maven – Welcome to Apache MavenMaven – Download Apache Maven 本文以最新版为例,可按需选择版本 Maven – Welcome to Apache Maven 2、解压下载好的安装包 将安装包解压到自己设置的空文件夹中 3、…

Pytorch学习之路 - CNN

目录 理论预热 实践 构建卷积神经网络 卷积网络模块构建 实战:基于经典网络架构训练图像分类模型 数据预处理部分: 网络模块设置: 网络模型保存与测试 实践 制作好数据源: 图片 标签 展示下数据 加载models中提供的模…

基于Spingboot+vue协同过滤音乐推荐管理系统

项目演示视频效果: 基于Spingbootvue协同过滤音乐推荐管理系统 基于Spingbootvue协同过滤音乐推荐管理系统 1、项目介绍 基于Springboot的音乐播放管理系统总共两个角色,用户和管理员。用户使用前端前台界面,管理员使用前端后台界面。 有推荐…

【A-025】基于SSH的房屋中介管理系统(含论文)

【A-025】基于SSH的房屋中介管理系统(含论文) 开发环境: Jdk7(8)Tomcat7(8)MySQLIntelliJ IDEA(Eclipse) 数据库: MySQL 技术: SpringStruts2HiberanteJquery 适用于: 课程设计,毕业设计&am…

python课后习题四

题目&#xff1a; 1. 2. 解题过程&#xff1a; 1. 单独创建一个MyTriangle模块 def isvalid(side1, side2, side3):return area(side1, side2, side3)def area(side1, side2, side3):if side1 side2 < side3 or side3 side2 < side1 or side3 side1 < side2:pr…

Spring Boot 如何实现缓存预热

Spring Boot 实现缓存预热 1、使用启动监听事件实现缓存预热。2、使用 PostConstruct 注解实现缓存预热。3、使用 CommandLineRunner 或 ApplicationRunner 实现缓存预热。4、通过实现 InitializingBean 接口&#xff0c;并重写 afterPropertiesSet 方法实现缓存预热。 1、使用…