写点什么

如何理解互斥锁、条件锁、读写锁以及自旋锁 (1),mysql 入门到精通电子书

用户头像
极客good
关注
发布于: 刚刚

操作系统负责线程调度,为了实现「锁的状态发生改变时再唤醒」就需要把锁也交给操作系统管理。所以互斥器的加锁操作通常都需要涉及到上下文切换,操作花销也就会比自旋锁要大。


以上两者的作用是加锁互斥,保证能够排它地访问被锁保护的资源。


不过并不是所有场景下我们都希望能够独占某个资源,很快你可能就会不得不写出这样的代码:


// 这是「生产者消费者问题」中的消费者的部分逻辑


// 等待队列非空,再从队列中取走元素进行处理


加锁(lock); // lock 保护对 queue 的操作


while (queue.isEmpty()) { // 队列为空时等待


解锁(lock);


// 这里让出锁,让生产者有机会往 queue 里安放数据


加锁(lock);


}


da


【一线大厂Java面试题解析+核心总结学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
复制代码


ta = queue.pop(); // 至此肯定非空,所以能对资源进行操作


解锁(lock);


消费(data); // 在临界区外做其它处理


你看那个 while,这不就是自己又搞了一个自旋锁么?区别在于这次你不是在 while 一个抽象资源是否可用,而是在 while 某个被锁保护的具体的条件是否达成。


有了前面自旋锁、互斥器的经验就不难想到:「只要条件没有发生改变,while 里就没有必要再去加锁、判断、条件不成立、解锁,完全可以让出 CPU 给别的线程」。不过由于「条件是否达成」属于业务逻辑,操作系统没法管理,需要让能够作出这一改变的代码来手动「通知」,比如上面的例子里就需要在生产者往 queue 里 push 后「通知」!queue.isEmpty() 成立。


也就是说,我们希望把上面例子中的 while 循环变成这样:


while (queue.isEmpty()) {


解锁后等待通知唤醒再加锁(用来收发通知的东西, lock);


}


生产者只需在往 queue 中 push 数据后这样,就可以完成协作:


触发通知(用来收发通知的东西);


// 一般有两种方式:


// 通知所有在等待的(notifyAll / broadcast)


// 通知一个在等待的(notifyOne / signal)


这就是条件变量(condition variable),也就是问题里的条件锁。它解决的问题不是「互斥」,而是「等待」。


至于读写锁(readers-writer lock),看英文可以顾名思义,在执行加锁操作时需要额外表明读写意图,复数读者之间并不互斥,而写者则要求与任何人互斥。读写锁不需要特殊支持就可以直接用之前提到的几个东西实现,比如可以直接用两个 spinlock 或者两个 mutex 实现:


void 以读者身份加锁(rwlock) {


加锁(rwlock.保护当前读者数量的锁);


rwlock.当前读者数量 += 1;


if (rwlock.当前读者数量 == 1) {


加锁(rwlock.保护写操作的锁);


}


解锁(rwlock.保护当前读者数量的锁);

用户头像

极客good

关注

还未添加个人签名 2021.03.18 加入

还未添加个人简介

评论

发布
暂无评论
如何理解互斥锁、条件锁、读写锁以及自旋锁(1),mysql入门到精通电子书