读写锁实现思路及简单测试

本文介绍了读写锁与互斥锁的区别,并详细阐述了读写锁的实现思路,包括状态标志、读者和写者计数、互斥量等关键设计。通过模拟测试,分析了读写锁在不同数据量下的性能表现,指出读写锁适用于读多写少的场景,可提升服务性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 读写锁与互斥锁的区别及读写锁应用场景

        对于后端数据服务维护人员来说,大多数数据类型服务,总是数据读取次数远大于数据修改次数,而读写锁的特殊机制非常适用于数据类型服务。近年来一直负责后端服务维护与功能开发,所负责的后端数据类型服务却都是采用互斥锁来进行数据保护,读写锁未尝一见,总是对其念念不忘,抽时间按照自己所想来实现一把,亲自揭开这个神秘的东东。

        互斥锁:顾名思义是排他性质,在任意时刻只有一个线程能获得该锁使用权。利用该特性,在多线程编程中用互斥锁保护数据,保证数据即便是在多线程环境下,同一时刻也只有一个获得互斥锁使用权的线程可进行数据读取或写入,其他需要访问数据的线程只有在互斥锁被释放后,获得该锁使用权才能进行数据操作。通过该种机制才能保障数据的完整性,有效性和程序的健壮性,杜绝脏数据,访问无效对象,服务崩溃等bug。互斥锁具有排他性,所有锁使用者只能串行运行

        读写锁:互斥锁不进行数据访问行为分类,不管数据读取还是数据写入,所有需要访问数据的线程被强制排队串行,依次进行数据访问。如果将数据访问行为分类,将读取数据划分为读者,数据写入划分为写者,根据数据访问行为逻辑可得到,所有读者线程因为仅仅进行数据读取,不会进行数据和数据结构的变更,在该前提下,所有读者线程是完全可以并行。在数据可读条件下,所有读者线程可同时访问数据。写者线程因为可能会对数据和数据结构进行变更,所以一旦写者线程获得了锁的使用权,那此时应该呈现互斥锁的排他性,只能该写者线程能访问数据,其他线程不论读者线程还是写者线程都只能等待锁释放后再抢占锁使用。读写锁提供只读时所有读者并行运行,只写时写者串行运行

        读写锁应用场景:根据上述对于互斥锁和读写锁的分析,可以很明显的看到读写锁并不是把所有需要锁应用线程全部串行化,而是灵活的根据需要允许部分线程并行,其余部分串行。正是可并行部分造成使用者相对使用互斥锁能提升部分数据访问性能。例如,大多数数据型服务基本都充当数据缓存进行数据分发,外加数据处理进行业务逻辑加工。数据分发型服务基本是读多写少业务处理,非常适合读写锁运行规则。如果在数据分发型服务中适当的使用读写锁替换互斥锁,能一定程度上提升服务性能。

2.实现思路

        根据读写锁运行机制,需要实现读锁并行,写锁串行逻辑。设计五个变量如下:

        1. 读写锁状态标志(0:不可读;1:可读;初始化可读)

        2. 读者计数(记录当前读者个数)

        3. 写者计数(记录当前写者个数)

        4. 共享互斥量(用于公共数据保护,既变量1、2、3数据保护)

        5. 写者互斥量(用于写者之间互斥)

        设计四个无参方法如下:

        1. 读者锁定。

        2. 读者解锁。

        3. 写者锁定。

        4. 写者解锁。

        附读锁申请、释放方法流程图如下:

         附写锁申请、释放方法流程图如下:

         关键细节:

        1. 利用锁状态标志来表明当前可读、还是只写的状态。该部分代码涉及到公共变量,需要互斥锁保护。

        2. 只写状态,使用写者互斥量实现写者之间的互斥,做到写状态串行运行。由于已经设置过写状态,所有读者都不能获取锁使用权。

        3. 读锁申请时,如果当前不可读,需要一直尝试直到可读为止。

        4. 写锁申请时,先设置状态标志为只写状态,防止有新的读者产生。之后需要检测读者个数,直到读者个数为0,才真正获得写权限,利用写者互斥锁串行数据操作。

        5. 写锁释放时,先释放写者互斥锁,然后修改写者计数,同时检测写者计数,如果是最后一个写者,那么需要重置状态标志为可读状态,使得读者有机会能获得读写锁使用权进行数据读取。

        特点:

        1. 采用写者设置状态标志量,能防止产生新的读者,使得写者具有一定优先处理权,不会使得写者饿死或处理延时过大的问题产生。

        2. 本实现借鉴自旋锁规则,在读者等待写者释放写锁和写者等待读者完成读取操作场景,没有采用主动阻塞自身的sleep或wait方法,而是和自旋锁一样,不断尝试获取锁使用权。(当然为了释放共享锁,使得其他类型线程有机会修改共享数据,采用无逻辑意义的空操作来暂时释放共享锁)这么设计的目的是为了减少线程切换带来的负担,最大化性能效率。当然弊端也很明显,会加重CPU消耗。

3.测试设置

        完成编码后,需要进行测试,看看读写锁和互斥锁的性能比较,是不是如想象中那么大的差别,在性能上有较为显著的提升。本人在查找资料时,在谋篇文章中提到,读写锁的性能和互斥锁差不多,甚至有时还不如互斥锁的性能。但是从读写锁原理分析,性能上是肯定有提升的,只不过可能没有想象中那么大幅度提升,再多分析终是不如实际测试。简单介绍测试工程配置和测试点如下:

        1. 为了模拟真实使用环境,设计工作线程数共十个。八个读者线程,两个写者线程。

        2. 每个读者线程读取数据一次后休眠五十毫秒,每个写者线程写入数据一次后s休眠五千毫秒,既五秒。

        3. 读者线程每次读取全量数据,写者线程每次判断数据量阈值,大于阈值删除十分之一数据量,小于阈值补充数据量至全量。

        4. 定义list为测试保护数据结构,定义六个int型字段为测试数据类型。

        5. 定义测试宏用于读写锁和互斥锁的测试切换。

        6. 分别在读写锁和互斥锁中添加统计数据,用于记录读取总数,该数据将直接体现两种锁下性能效率,在读写锁中,添加最大读者并发数,用于分析性能参考。

        7. 主线程定时三十秒输出一次统计数据。

        8. 数据量分为,500、1000、3000、5000四个量级。毕竟不需要多精确,只需要有一个大致的性能趋势对比即可得到结论。

4.测试结果

数据量互斥锁读取平均数互斥锁CPU占用率读写锁读取平均数读写锁CPU占用率读写锁读取并行数
50047003%4760

2.5%-5.2%

均值4%

5
100046008%4680

5.2%-13%

均值7%

7
3000420020%4350

14%-30%

均值17%

7
5000320038%3760

26%-46%

均值32%

8

        关于测试的一些备注:

        1. 设计两个写者是为了测试读写锁的逻辑完整性和安全性,实际线上环境大部分是单一写者较为常见。

        2. 设计数据量级仅分为四个,基本能根据数据量级可得到处理性能曲线和大致趋势,并未继续增大数据量级,各位读者如有兴趣可自行测试完善。且该设计数据量级也符合实际线上环境,如果有更大量级数据,可在设计时将数据进行哈希分组,最终的结果和测试数据量级相接近。

        3. 测试并未设计复杂和耗时的读取和写入过程,仅做全量数据拷贝和十分之一量数据增删。用来模拟实际线上环境。

        4. 测试锁的CPU占用情况,可以很清晰的看到互斥锁CPU占用较为稳定,随着数据量增大,CPU占用线性提升。读写锁的CPU占用,有较强的随机性,CPU波动较大上下限差别较大,根据实际观察,CPU的平均占用较互斥锁略低。

        5. 测试设计数据类型为常用数据类型,并未设计较为复杂或字段较多的结构体,个人认为本设计已经可以涵盖大部分实际线上环境。各位读者如有需要或兴趣可自行测试。

        6. 读写锁部分的并行读者计数,由于是随着定时输出的随机获取,并不精确,但也能看到一定规律。随着数据量的增加,并发数趋于最大化。简单分析即可得到结论,本处不再展开。如有疑问可以提出。

5.读写锁应用之个人结论

        1. 根据测试结果可以看到,在数据量较小时读写锁和互斥锁性能较为接近,互斥锁在代码量和CPU占用方面有微弱优势。(看到该量级测试对比时,想起来看过的一篇帖子说,读写锁性能反而不如互斥锁,可能说的就是这种情况)

        2. 当数据量提升,读写锁较互斥锁更具有韧性,性能下降比互斥锁更缓慢,CPU占用方面也比互斥锁略低,各方面参数较互斥锁更优秀。

        3. 读写锁在读者等待写者释放、写者等待读者释放的空操作时,不能完全采用自旋锁形式,可以适当限定尝试一定量后主动休眠,释放CPU资源,能进一步降低CPU占用情况。但可能会降低处理性能。

        4. 根据测试结果,个人认为读写锁适用于读多写少的应用场景,且应有一定的数据量,或是数据处理较为耗时的情况,读写锁的表现都将优于互斥锁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值