多线程之读者写者模型
什么是读者写者问题?
为了能理解这个概念我们先举个列子:
我们在小时候,通常有一个东西叫做——黑板报!在一个班级上,有一个叫小明的学生,他字写的很高,有一天他正在画黑板报,同学们在他旁边看,窃窃私语的猜他在画什么东西!
有的猜说画的是蛇,有的说画的是龙,等等但是到最后其实画的是一个绳子——当小明正在出黑板报的时候!其他人读取到的只是内容的局部性的东西!最终读取的数据都是不正确的!只拿了一部分!
为了保证每一位学生读取到的数据是完整的,于是班级里面做了一个规定,在小明画完之前,大家都不能看!——小明要么就不出,要么就出完
小明将黑板报出完了之后!班级上的同学一个个的都围过来看!愉快的进行讨论!,那么讨论的时候,会不会说一个个都排好队,分别来看?或者会不会说我先来的!你们都闭上眼是我先来看?——肯定是不会的!都是一起看的!
但是出黑板报的时候!会不会有一个同学正在一个地方画,另一个同学说同时也想要在一个地方画的情况出现呢?——不会!肯定是一个个的来画
==对于读者写者问题,有三种关系!两种角色!一种交易场所!==
一个交易场所就是——黑板报,是一份共享资源!
两种关系就是指——读者与写者
对于出黑板的小明和其他出黑板报的人——就是典型的写者!
对于看黑板报的其他同学——就是典型的读者
==出黑板班的过程!其实本质就是一个读者写者问题!==
是三种关系:
- 写者和写者是什么关系?——小明和其他出黑板报的人是什么关系?是互斥关系总不能出现我要画画,它要写字,在同一个地方搞,我刚刚画完,另一个人就擦了写字!或者说反过来!==所以写者和写者之间是典型的互斥关系!==
- 读者和写者之间是什么关系?——我们上面说过!如果正在出黑板报的时候!其他同学来读取!那么就可能读取到的是残缺的数据!为了让读者能够读取到完整的数据!我们一定要保证!写者要么不写!要么写完后才能被允许读!==所以读者和写者之间就也是互斥关系!==(写的时候不要读!)
除此之外!如果黑板报已经很久没有更新了,那么同学去读也没有意思!所以应该让写者去更新新的黑板报!或者这个黑板报是这学期搞的!到了下学期就被擦了!那么此时同学也就没得看了!——==所以读者和写者之间也是有一定的同步关系!==
- 读者和读者是什么关系?——==没有任何关系!==不需要互斥!不需要同步!——就像上面说的看黑板报的时候,是不会有人要求排队一个个看的!出现一个人看的时候,另一个人不能看!
对比生成消费模型,与消费者的其实是一模一样的!区别就是读者和消费者
==读者写者模型与生成消费模型的本质区别是什么——消费者会拿走数据!而读者不会!==
读者只是把数据拷贝走一份,访问一下!数据是还在的!所以就注定了读者和读者之间是没有任何关系!无论访问多少次数据都是还在的!
而消费者会把数据拿走!拿走后别人就没了!——所以消费者与消费者之间是互斥关系!
==那么场景下面我们会使用到读者写者模型呢?——属于一次发布!很长时间不做修改的场景!大部分的时间都是被读取的!(例如:博客,新闻这些东西,我们一般发出来之后,如果没有什么错误!否则很长时间不会修改!而是被人读取!)==
写博客的人就是写者!或者写新闻的人也是写者!
除此之外还有,比如现在公司给人提供互联网产品不一定是非得是app,有可能是某种接口式的服务!例如:别人想知道当前位置的坐标!我们可以提供一个网络服务,让别人去调用一个接口,就可以知道了!
这个服务接口一旦发布就很长时间不会改变了!这也是一种典型的读者写者问题!
或者说是我们程序的配置问题!当我们程序启动的时候,就会去读取这个配置文件,而我们大部分情况下都很少会去修改配置文件!这也是一种读者写者模型!
==总结只要我们发布出去的东西很长时间不会修改!大部分时间都是被读取!那么那就是读者写者问题!==
所以这也是读者写者问题的特点!大部分时间在读取!少部分时间在写入!
读写锁及其相关接口
在编写多线程的时候,有一种情况是十分常见的。那就是,有些公共数据修改的机会比较少。相比较改写,它们读的机会反而高的多。通常而言,在读的过程中,往往伴随着查找的操作,中间耗时很长。
给这种代码段加锁,会极大地降低我们程序的效率。那么有没有一种方法,可以专门处理这种多读少写的情况呢?有,那就是读写锁。——用于解决读者写者问题!
==就是说读锁存在的时候可以被多个线程申请!但是只有写锁无法申请!==
==写锁存在的时候!只有一个线程可以持有锁!其他线程无论是读锁还是写锁都无法申请!==
初始化和销毁接口
pthread_rwlock_t就是读写锁的类型!
因为读的角色和写的角色未来是两种角色,所以使用==加锁方式==也是不一样的!
读者加锁方式
这就是读加锁的方案!
写者加锁的方式
==但是解锁的方式都是一样的!无论是读者还是写者!==
我们根据上面的概念!我们可以知道!——==在任意一个时间!只允许一个读者写入!但是可能允许多个读者读取(但是此时写者阻塞)!==
读写锁的原理
标签:黑板报,加锁,读取,写者,优先,读者,字长,多线程 From: https://blog.51cto.com/u_15835985/9481447==系统是如何让同一个锁来实现两种不同的加锁行为的呢?==
pthread_rwlock_t;//这是一个结构体! //我们可以任务这个结构体里面有两把锁! //rdlock和wrlock
后面写锁的加锁其实本质都是对于计数器进行++,rdlock的用处本质是用来保护共享资源!
写者的逻辑就是十分的简单!
==如果写者先到,那么就会因为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 写者优先,但写者不能递归加锁 */