【Linux】读写锁和自旋锁

简介: 在编写多线程的时候,有一种情况是十分常见的。那就是,有些公共数据修改的机会比较少;相比较改写,它们读的机会反而高的多。通常而言,在读的过程中,往往伴随着查找的操作,中间耗时很长,再给这种代码段加锁,会极大地降低我们程序的效率。那么有没有一种方法,可以专门处理这种多读少写的情况呢? 有,那就是读写锁。


一. 读写锁


1. 什么是读写锁?


在编写多线程的时候,有一种情况是十分常见的。那就是,有些公共数据修改的机会比较少;相比较改写,它们读的机会反而高的多。通常而言,在读的过程中,往往伴随着查找的操作,中间耗时很长,再给这种代码段加锁,会极大地降低我们程序的效率。那么有没有一种方法,可以专门处理这种多读少写的情况呢? 有,那就是读写锁。


读写锁的主要功能是维护以下三种关系:


读读共享:读者可以同时访问临界资源。

写写互斥:写者之间择其一访问临界资源。

读写互斥:写者在写的时候读者不能访问临界资源,反之亦然。

这三种关系对读者、写者的具体作用效果体现在下面表格中:



当前锁的状态 读锁请求 写锁请求

无锁 可以 可以

读锁 可以 阻塞

写锁 阻塞 阻塞

读写锁的使用场景


场景一:写入操作少,读取操作多。

场景二:数据写入之后,剩下的操作就是读取。

PS:读写锁的设计是读锁优先。即当读者线程和写者线程同时去竞争同一把读写锁时,读者线程优先级更高。这样设计的目的是为了优先满足更多用户的需求,少数服从多数。


读者写者模式 VS 生产者消费者模式


生产者相当于写者。

消费者不同于读者。区别是消费者会取走数据,而读者不会。

读写锁概述

fc776e248afb4cc6ba9667be2fc57646.png



2. 为什么要有读写锁?


读写锁将操作分为读、写两种方式,可以多个线程同时占用读模式的读写锁,这样使得程序具有更高的并行性。


有时候,在多线程中,有一些公共数据修改的机会比较少,而读的机会却是非常多的,此公共数据的操作基本都是读,如果每次操作都给此段代码加锁,太浪费时间了而且也很浪费资源,降低程序的效率,因为读操作不会修改数据,只是做一些查询,所以在读的时候考虑不用给此段代码加锁,让读者可以共享的访问,只有涉及到写的时候,互斥的访问就好了。


3. POSIX下的读写锁相关接口函数

cd0f530f9de84a66a0dd883bd619f382.png



4. 读写锁实现原理


读写锁的本质是:互斥锁封装而成的写独占,读共享,读锁优先级高的锁。


下面是读写锁实现原理的伪代码:

c63c3d23693a49e2ba5e1daf687badb0.png


二. 自旋锁


1. 什么是自旋锁?

自旋锁(spinlock):是为实现保护临界资源而提出的一种轻量级锁机制,当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。


其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个线程能持有锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,则资源申请者只能进入睡眠状态(进行上下文切换和任务调度)。但是自旋锁不会引起申请者睡眠,如果自旋锁已经被别的线程持有,那么申请者就一直忙循环检查该自旋锁的保持者已经释放了锁,”自旋”一词就是因此而得名。


有个经典的例子来比喻自旋锁:在一个宿舍中,共用一个厕所,那么这个厕所就是临界资源,且在任一时刻最多只能有一个人在使用。当厕所闲置时,谁来了都可以使用。当甲在里面蹲坑时,就会关上厕所门,而乙也要使用,但是急啊,就得在门外焦急地等待,急得团团转,是为“自旋”。

ef7f250b2b4d43b28876d125aeb03157.png


至于互斥锁可以理解为还是那个宿舍的厕所,甲方正在里面洗澡,本来乙方也要洗,但是看到里面有人后决定先上床睡一觉,顺便等甲洗完后自己了再来洗,这里的睡一觉相当于线程进入休眠状态,睡之前的脱衣工作相当于线程的上下文切换和任务调度。



9a6a16bbc7fb4828a4fa1506d8168ca8.png

自旋锁特性


在任何时刻最多只能有一个线程获得自选锁。

要求持有锁的处理器所占用的时间尽可能短。

等待锁的线程进入忙循环,而不是休眠等待。

不需要进行上下文切换和任务调度。


2. 为什么要有自旋锁?


确定线程进入临界资源时间较短时可以考虑使用自旋锁,这样等待申请锁的线程一直处于忙循环的状态去监测锁资源是否被释放,而不是休眠等待,所以页不需要进行上下文切换和任务调度。


如果线程进入临界资源时间比较长的话,就不宜使用自旋锁,因为等待锁的线程会消耗过多CPU资源:如果申请不成功,申请者将一直循环判断,这无疑降低了CPU的使用率。这时用互斥锁比较好,它们让申请锁的线程进入睡眠等待状态。


PS:在实际使用时,不论是自旋锁还是互斥锁,我们看到的现象都是:申请不到锁的线程停止不动、被阻塞住了,实际上二者对线程的“阻塞方式”是不同的。


3. POSIX下的自旋锁相关接口函数


1b240bafb12a41beb4f56e56b384094c.png


4. 自旋锁实现原理

0f98bed9e2a2446e9f0f76a1552c2570.png

相关文章
|
5天前
|
Unix Shell Linux
linux互斥锁(pthread_mutex)知识点总结
linux互斥锁(pthread_mutex)知识点总结
|
5天前
|
算法 安全 Linux
【探索Linux】P.20(多线程 | 线程互斥 | 互斥锁 | 死锁 | 资源饥饿)
【探索Linux】P.20(多线程 | 线程互斥 | 互斥锁 | 死锁 | 资源饥饿)
12 0
|
5天前
|
Linux 数据安全/隐私保护
Linux 读写权限的配置
Linux 读写权限的配置
13 0
|
5天前
|
Linux API C++
【Linux C/C++ 线程同步 】Linux API 读写锁的编程使用
【Linux C/C++ 线程同步 】Linux API 读写锁的编程使用
22 1
|
5天前
|
Linux 调度 C语言
【Linux C/C++ 线程同步 】Linux互斥锁和条件变量:互斥锁和条件变量在Linux线程同步中的编程实践
【Linux C/C++ 线程同步 】Linux互斥锁和条件变量:互斥锁和条件变量在Linux线程同步中的编程实践
48 0
|
5天前
|
安全 Linux C语言
【Linux权限:系统中的数字锁与安全之门】(下)
【Linux权限:系统中的数字锁与安全之门】
|
5天前
|
安全 Linux 数据安全/隐私保护
【Linux权限:系统中的数字锁与安全之门】(上)
【Linux权限:系统中的数字锁与安全之门】
|
5天前
|
Linux C语言
linux c 多线程 互斥锁、自旋锁、原子操作的分析与使用
生活中,我们常常会在12306或者其他购票软件上买票,特别是春节期间或者国庆长假的时候,总会出现抢票的现象,最后总会有人买不到票而埋怨这埋怨那,其实这还好,至少不会跑去现场或者网上去找客服理论,如果出现了付款,但是却没买到票的现象,那才是真的会出现很多问题,将这里的票引入到多线程中,票就被称为临界资源。
32 0
|
5天前
|
Linux
Linux在文件特定偏移量处读写pread和pwrite
系统调用 pread()和 pwrite()完成与 read()和 write()相类似的工作,只是前两者会在 offset 参数所指定的位置进行文件 I/O 操作,而非始于文件的当前偏移量处,且它们不会改变文件的当前偏移量。
60 0
Linux在文件特定偏移量处读写pread和pwrite
|
5天前
|
Linux
Linux io多块读写readv函数和writev函数
fd参数是被操作的目标文件描述符。iov参数的类型是iovec结构数组,该结构体描述一块内存区。iovcnt参数是iov数组的长度,即有多少块内存数据需要从fd读出或写到fd。readv和writev在成功时返回读出/写入fd的字节数,失败则返回-1并设置errno。readv函数将数据从文件描述符读到分散的内存块中,即分散读;writev函数则将多块分散的内存数据一并写入文件描述符中,即集中写。
23 0