首页 > 其他分享 >多线程之读者写者模型(三千字长文详解)

多线程之读者写者模型(三千字长文详解)

时间:2024-01-30 11:31:51浏览次数:35  
标签:黑板报 加锁 读取 写者 优先 读者 字长 多线程

多线程之读者写者模型

什么是读者写者问题?

为了能理解这个概念我们先举个列子:

我们在小时候,通常有一个东西叫做——黑板报!在一个班级上,有一个叫小明的学生,他字写的很高,有一天他正在画黑板报,同学们在他旁边看,窃窃私语的猜他在画什么东西!

有的猜说画的是蛇,有的说画的是龙,等等但是到最后其实画的是一个绳子——当小明正在出黑板报的时候!其他人读取到的只是内容的局部性的东西!最终读取的数据都是不正确的!只拿了一部分!

为了保证每一位学生读取到的数据是完整的,于是班级里面做了一个规定,在小明画完之前,大家都不能看!——小明要么就不出,要么就出完

小明将黑板报出完了之后!班级上的同学一个个的都围过来看!愉快的进行讨论!,那么讨论的时候,会不会说一个个都排好队,分别来看?或者会不会说我先来的!你们都闭上眼是我先来看?——肯定是不会的!都是一起看的!

但是出黑板报的时候!会不会有一个同学正在一个地方画,另一个同学说同时也想要在一个地方画的情况出现呢?——不会!肯定是一个个的来画

==对于读者写者问题,有三种关系!两种角色!一种交易场所!==

一个交易场所就是——黑板报,是一份共享资源!

两种关系就是指——读者与写者

  1. 对于出黑板的小明和其他出黑板报的人——就是典型的写者!

  2. 对于看黑板报的其他同学——就是典型的读者

==出黑板班的过程!其实本质就是一个读者写者问题!==

是三种关系:

  1. 写者和写者是什么关系?——小明和其他出黑板报的人是什么关系?是互斥关系总不能出现我要画画,它要写字,在同一个地方搞,我刚刚画完,另一个人就擦了写字!或者说反过来!==所以写者和写者之间是典型的互斥关系!==
  2. 读者和写者之间是什么关系?——我们上面说过!如果正在出黑板报的时候!其他同学来读取!那么就可能读取到的是残缺的数据!为了让读者能够读取到完整的数据!我们一定要保证!写者要么不写!要么写完后才能被允许读!==所以读者和写者之间就也是互斥关系!==(写的时候不要读!)

除此之外!如果黑板报已经很久没有更新了,那么同学去读也没有意思!所以应该让写者去更新新的黑板报!或者这个黑板报是这学期搞的!到了下学期就被擦了!那么此时同学也就没得看了!——==所以读者和写者之间也是有一定的同步关系!==

  1. 读者和读者是什么关系?——==没有任何关系!==不需要互斥!不需要同步!——就像上面说的看黑板报的时候,是不会有人要求排队一个个看的!出现一个人看的时候,另一个人不能看!

对比生成消费模型,与消费者的其实是一模一样的!区别就是读者和消费者

==读者写者模型与生成消费模型的本质区别是什么——消费者会拿走数据!而读者不会!==

读者只是把数据拷贝走一份,访问一下!数据是还在的!所以就注定了读者和读者之间是没有任何关系!无论访问多少次数据都是还在的!

而消费者会把数据拿走!拿走后别人就没了!——所以消费者与消费者之间是互斥关系!

==那么场景下面我们会使用到读者写者模型呢?——属于一次发布!很长时间不做修改的场景!大部分的时间都是被读取的!(例如:博客,新闻这些东西,我们一般发出来之后,如果没有什么错误!否则很长时间不会修改!而是被人读取!)==

写博客的人就是写者!或者写新闻的人也是写者!

除此之外还有,比如现在公司给人提供互联网产品不一定是非得是app,有可能是某种接口式的服务!例如:别人想知道当前位置的坐标!我们可以提供一个网络服务,让别人去调用一个接口,就可以知道了!

这个服务接口一旦发布就很长时间不会改变了!这也是一种典型的读者写者问题!

或者说是我们程序的配置问题!当我们程序启动的时候,就会去读取这个配置文件,而我们大部分情况下都很少会去修改配置文件!这也是一种读者写者模型!

==总结只要我们发布出去的东西很长时间不会修改!大部分时间都是被读取!那么那就是读者写者问题!==

所以这也是读者写者问题的特点!大部分时间在读取!少部分时间在写入!

读写锁及其相关接口

在编写多线程的时候,有一种情况是十分常见的。那就是,有些公共数据修改的机会比较少。相比较改写,它们读的机会反而高的多。通常而言,在读的过程中,往往伴随着查找的操作,中间耗时很长。

给这种代码段加锁,会极大地降低我们程序的效率。那么有没有一种方法,可以专门处理这种多读少写的情况呢?有,那就是读写锁。——用于解决读者写者问题!

image-20230828203126092

==就是说读锁存在的时候可以被多个线程申请!但是只有写锁无法申请!==

==写锁存在的时候!只有一个线程可以持有锁!其他线程无论是读锁还是写锁都无法申请!==

初始化和销毁接口

image-20230828201734387

pthread_rwlock_t就是读写锁的类型!

因为读的角色和写的角色未来是两种角色,所以使用==加锁方式==也是不一样的!

读者加锁方式

image-20230828202121443

这就是读加锁的方案!

写者加锁的方式

image-20230828202217586

==但是解锁的方式都是一样的!无论是读者还是写者!==

image-20230828203302328

我们根据上面的概念!我们可以知道!——==在任意一个时间!只允许一个读者写入!但是可能允许多个读者读取(但是此时写者阻塞)!==

读写锁的原理

==系统是如何让同一个锁来实现两种不同的加锁行为的呢?==

pthread_rwlock_t;//这是一个结构体!
//我们可以任务这个结构体里面有两把锁!
//rdlock和wrlock

image-20230828212503928

后面写锁的加锁其实本质都是对于计数器进行++,rdlock的用处本质是用来保护共享资源!

image-20230828212940818

写者的逻辑就是十分的简单!

==如果写者先到,那么就会因为wrlock被加锁了!导致读者那边无法拿到wrlock从而导致被阻塞,从而实现!写者写入的时候!读者无法读取!==

==如果读者先到!也是同理!先持有了wlock从而让写者无法持有!从而导致写者被阻塞!==

那么他们使用的是同一个函数来进行解锁!为什么会逻辑不一样呢?——pthread_rwlock_t是一个结构体,可以知道到底是此时到底是读者在使用还是写者在使用!肯定有一定的策略来解决!

==如果在读者非常多,但是写者只有一个的情况下!==

如果读者正在读的时候!写者是无法写的!因为只要计数器大于0,那么写者就无法持有锁!

那么如果有写者一直过来!那么就会让写者长时间无法得到资源!——==这就是写者饥饿问题!==

读者写者模型下面——写者饥饿问题出现是很正常的!因为大部分情况都是在读取!

==这种让读者来了默认去读——这种就是读者优先!==(只要读者一直来!那么写者就要一直等!)==上面我们说的接口默认都是读者优先的!==

因为这种读者一直来的情况是很少的!写者总有机会去写入!只不过可能慢了一点!

==写者优先是什么情况呢?==

如果此时有一个写者十个读者,有五个正在读,然后此时一个读者和一个写者同时访问!

按读者优先来说,因为前面有5个读者正在读!那么肯定是读者进!而写者不进

但是如果是写者优先——写者拦不住已经进去访问的!所以注定了写者还是要等!但是写者是能拦得住还没有进去的线程!凡是比写者到来的线程,可以去读取!但是比写者晚的!那么就都别进去,等前面的读者先读取完!然后计数器到0,写者就可以进去写入后,后面的读者才能进去!==这就是写者优先!==

如果想要实现写者优先,那么就得多一把锁或者条件变量!

当读者到了的时候,就多申请一下这个锁!条件不满足就全部阻塞!

==读者优先和写者优先就是一种同步策略!==

//设置优先级的函数
int pthread_rwlockattr_setkind_np(pthread_rwlockattr_t *attr, int pref);
/*
pref 共有 3 种选择
PTHREAD_RWLOCK_PREFER_READER_NP (默认设置) 读者优先,可能会导致写者饥饿情况
PTHREAD_RWLOCK_PREFER_WRITER_NP 写者优先,目前有 BUG,导致表现行为和
PTHREAD_RWLOCK_PREFER_READER_NP 一致
PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP 写者优先,但写者不能递归加锁
*/

标签:黑板报,加锁,读取,写者,优先,读者,字长,多线程
From: https://blog.51cto.com/u_15835985/9481447

相关文章

  • 一次因PageHelper引起的多线程复用问题的排查和解决 | 京东物流技术团队
    A、ProblemDescription1.PageHelper方法使用了静态的ThreadLocal参数,在startPage()调用紧跟MyBatis查询方法后,才会自动清除ThreadLocal存储的对象。2.当一个线程先执行了A方法的PageHelper.startPage(intpageNum,intpageSize)后,在未执行到SQL语句前,因为代码抛异常而提前结束......
  • 深入浅出Java多线程(三):线程与线程组
    「引言」大家好,我是你们的老伙计秀才!今天带来的是[深入浅出Java多线程]系列的第三篇内容:线程与线程组。大家觉得有用请点赞,喜欢请关注!秀才在此谢过大家了!!!在现代软件开发中,多线程编程已成为提升程序性能和并发能力的关键技术之一。Java作为主流的面向对象编程语言,其对多线程的支......
  • java用多线程批次查询大量数据(Callable返回数据)方式
    我看到有的数据库是一万条数据和八万条数据还有十几万条,几百万的数据,然后我就想拿这些数据测试一下,发现如果用java和数据库查询就连一万多条的数据查询出来就要10s左右,感觉太慢了。然后网上都说各种加索引,加索引貌似是有查询条件时在某个字段加索引比较快一些,但是毕竟是人家的库不......
  • 深入浅出Java多线程(二):Java多线程类和接口
    引言大家好,我是你们的老伙计秀才!今天带来的是[深入浅出Java多线程]系列的第二篇内容:Java多线程类和接口。大家觉得有用请点赞,喜欢请关注!秀才在此谢过大家了!!!在现代计算机系统中,多线程技术是提升程序性能、优化资源利用和实现并发处理的重要手段。特别是在Java编程语言中,多线程机......
  • 多线程
    多线程理论(1)什么是线程在Python中,线程(Thread)是执行单元的最小单位。线程是进程内的一条执行路径,每个线程都有自己的执行序列、执行环境和栈空间,但它们共享同一个进程的地址空间。在多线程编程中,可以同时运行多个线程,每个线程执行不同的任务,从而实现并发执行。相比于多进......
  • C++多线程 第一章 你好,C++并发世界
    第一章你好,C++并发世界C++并发并发(concurrency):主要包括任务切换与硬件并发两类.并发(concurrency)实际上与多线程(multithreading)存在差异.并发的种类任务切换(taskswitching):计算机在某一时刻只可以真正执行一个任务,但它可以每秒切换任务许多次.通过做一......
  • linux之自旋锁(二千字长文)
    linux之自旋锁常见的各种锁悲观锁:在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁,写锁,行锁等),当其他线程想要访问数据时,被阻塞挂起。总是先行认为数据一定会被修改!所以要先加锁!保证没有人能够访问它!==我们学的同步互斥机制!其实都是属于悲观锁的范畴!==......
  • 实现多线程的方式有哪几种?
    Java虚拟机时是运行所有Java程序的抽象计算机,允许应用并发的运行多个线程。在Java语言中,多线程的实现,一般有以下3中方法:1.实现Runnable接口,并实现该接口的run()方法;主要步骤:1.自定义类并实现Runnable接口,实现run()方法;2.创建Thread类,用实现Runnable接口的对象作为参数实例化......
  • python 多线程运行 串行或并行
    我们知道在python中运行多线程程序很简单,只需要几步,创建线程,start线程即可,下面简单说下多线程的串行或者并行的使用示例:#-*-coding:utf-8-*-#@Time:2024-01-2714:03importthreadingimporttimedefrun(name:str)->None:time.sleep(3)print("Thre......
  • 多线程简单介绍
    线程:是操作系统能够进行运算调度的最小单位。它被包含在进程中,是进程中实际运作单位进程:进程是程序的基本执行实体,一个程序就是一个进程简单理解线程:应用软件中相互独立,可以同时运行的功能有了多线程,就可以让程序同时做多件事线程的生命周期完整的线程状态New(新建状态)->......