一. 读写锁
1. 什么是读写锁?
在编写多线程的时候,有一种情况是十分常见的。那就是,有些公共数据修改的机会比较少;相比较改写,它们读的机会反而高的多。通常而言,在读的过程中,往往伴随着查找的操作,中间耗时很长,再给这种代码段加锁,会极大地降低我们程序的效率。那么有没有一种方法,可以专门处理这种多读少写的情况呢? 有,那就是读写锁。
读写锁的主要功能是维护以下三种关系:
读读共享:读者可以同时访问临界资源。
写写互斥:写者之间择其一访问临界资源。
读写互斥:写者在写的时候读者不能访问临界资源,反之亦然。
这三种关系对读者、写者的具体作用效果体现在下面表格中:
当前锁的状态 读锁请求 写锁请求
无锁 可以 可以
读锁 可以 阻塞
写锁 阻塞 阻塞
读写锁的使用场景
场景一:写入操作少,读取操作多。
场景二:数据写入之后,剩下的操作就是读取。
PS:读写锁的设计是读锁优先。即当读者线程和写者线程同时去竞争同一把读写锁时,读者线程优先级更高。这样设计的目的是为了优先满足更多用户的需求,少数服从多数。
读者写者模式 VS 生产者消费者模式
生产者相当于写者。
消费者不同于读者。区别是消费者会取走数据,而读者不会。
读写锁概述
2. 为什么要有读写锁?
读写锁将操作分为读、写两种方式,可以多个线程同时占用读模式的读写锁,这样使得程序具有更高的并行性。
有时候,在多线程中,有一些公共数据修改的机会比较少,而读的机会却是非常多的,此公共数据的操作基本都是读,如果每次操作都给此段代码加锁,太浪费时间了而且也很浪费资源,降低程序的效率,因为读操作不会修改数据,只是做一些查询,所以在读的时候考虑不用给此段代码加锁,让读者可以共享的访问,只有涉及到写的时候,互斥的访问就好了。
3. POSIX下的读写锁相关接口函数
4. 读写锁实现原理
读写锁的本质是:互斥锁封装而成的写独占,读共享,读锁优先级高的锁。
下面是读写锁实现原理的伪代码:
二. 自旋锁
1. 什么是自旋锁?
自旋锁(spinlock):是为实现保护临界资源而提出的一种轻量级锁机制,当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。
其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个线程能持有锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,则资源申请者只能进入睡眠状态(进行上下文切换和任务调度)。但是自旋锁不会引起申请者睡眠,如果自旋锁已经被别的线程持有,那么申请者就一直忙循环检查该自旋锁的保持者已经释放了锁,”自旋”一词就是因此而得名。
有个经典的例子来比喻自旋锁:在一个宿舍中,共用一个厕所,那么这个厕所就是临界资源,且在任一时刻最多只能有一个人在使用。当厕所闲置时,谁来了都可以使用。当甲在里面蹲坑时,就会关上厕所门,而乙也要使用,但是急啊,就得在门外焦急地等待,急得团团转,是为“自旋”。
至于互斥锁可以理解为还是那个宿舍的厕所,甲方正在里面洗澡,本来乙方也要洗,但是看到里面有人后决定先上床睡一觉,顺便等甲洗完后自己了再来洗,这里的睡一觉相当于线程进入休眠状态,睡之前的脱衣工作相当于线程的上下文切换和任务调度。
自旋锁特性
在任何时刻最多只能有一个线程获得自选锁。
要求持有锁的处理器所占用的时间尽可能短。
等待锁的线程进入忙循环,而不是休眠等待。
不需要进行上下文切换和任务调度。
2. 为什么要有自旋锁?
确定线程进入临界资源时间较短时可以考虑使用自旋锁,这样等待申请锁的线程一直处于忙循环的状态去监测锁资源是否被释放,而不是休眠等待,所以页不需要进行上下文切换和任务调度。
如果线程进入临界资源时间比较长的话,就不宜使用自旋锁,因为等待锁的线程会消耗过多CPU资源:如果申请不成功,申请者将一直循环判断,这无疑降低了CPU的使用率。这时用互斥锁比较好,它们让申请锁的线程进入睡眠等待状态。
PS:在实际使用时,不论是自旋锁还是互斥锁,我们看到的现象都是:申请不到锁的线程停止不动、被阻塞住了,实际上二者对线程的“阻塞方式”是不同的。
3. POSIX下的自旋锁相关接口函数