如何理解互斥锁、条件锁、读写锁以及自旋锁?(转载)
自旋锁(spinlock)很好理解。对自旋锁加锁的操作,你可以认为是类似这样的:
while (抢锁(lock) == 没抢到) {}
只要没有锁上,就不断重试。显然,如果别的线程长期持有该锁,那么你这个线程就一直在 while while while 地检查是否能够加锁,浪费 CPU 做无用功。
仔细想想,其实没有必要一直去尝试加锁,因为只要锁的持有状态没有改变,加锁操作就肯定是失败的。所以,抢锁失败后只要锁的持有状态一直没有改变,那就让出 CPU 给别的线程先执行好了。这就是互斥器(mutex)也就是题目里的互斥锁(不过个人觉得既然英语里本来就不带 lock,就不要称作锁了吧)。对互斥器加锁的操作你可以认为是类似这样的:
while (抢锁(lock) 没抢到) {本线程先去睡了请在这把锁的状态发生改变时再唤醒(lock);
}
操作系统负责线程调度,为了实现「锁的状态发生改变时再唤醒」就需要把锁也交给操作系统管理。所以互斥器的加锁操作通常都需要涉及到上下文切换,操作花销也就会比自旋锁要大。
以上两者的作用是加锁互斥,保证能够排它地访问被锁保护的资源。
不过并不是所有场景下我们都希望能够独占某个资源,很快你可能就会不得不写出这样的代码:
// 这是「生产者消费者问题」中的消费者的部分逻辑
// 等待队列非空,再从队列中取走元素进行处理加锁(lock); // lock 保护对 queue 的操作
while (queue.isEmpty()) { // 队列为空时等待解锁(lock);// 这里让出锁,让生产者有机会往 queue 里安放数据加锁(lock);
}
data = 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.保护当前读者数量的锁);
}
void 以读者身份解锁(rwlock) {加锁(rwlock.保护当前读者数量的锁);
rwlock.当前读者数量 -= 1;
if (rwlock.当前读者数量 == 0) {解锁(rwlock.保护写操作的锁);
}
解锁(rwlock.保护当前读者数量的锁);
}void 以写者身份加锁(rwlock) {加锁(rwlock.保护写操作的锁);
}void 以写者身份解锁(rwlock) {解锁(rwlock.保护写操作的锁);
}
如果整个场景中只有一个读者、一个写者,那么其实可以等价于直接使用互斥器。不过由于读写锁需要额外记录读者数量,花销要大一点。
你可以认为读写锁是针对某种特定情景的「优化」。但个人还是建议忘掉读写锁,直接用互斥器。
如何理解互斥锁、条件锁、读写锁以及自旋锁?(转载)相关推荐
- 多线程编程之Apue3rd_Chapter11之互斥锁_读写锁_自旋锁
学习了apue3rd的第11章,主要讲的是多线程编程.因为线程共享进程的资源比如堆和全局变量,多线程编程最重要的是,使用各种锁进行线程同步. 线程编程首先要学习的三个函数如下: #include &l ...
- 线程同步(互斥锁、条件、读写锁、信号量)
参考:(四十三)线程--线程同步(互斥锁.读写锁.条件变量.信号量) 作者:FadeFarAway 发布时间:2017-01-17 21:25:28 网址:https://blog.csdn.net/ ...
- 嵌入式 自旋锁、互斥锁、读写锁、递归锁
互斥锁(mutexlock): 最常使用于线程同步的锁:标记用来保证在任一时刻,只能有一个线程访问该对象,同一线程多次加锁操作会造成死锁:临界区和互斥量都可用来实现此锁,通常情况下锁操作失败会将该线程 ...
- 华为应用锁退出立即锁_面试官:你说说互斥锁、自旋锁、读写锁、悲观锁、乐观锁的应用场景...
前言 生活中用到的锁,用途都比较简单粗暴,上锁基本是为了防止外人进来.电动车被偷等等. 但生活中也不是没有 BUG 的,比如加锁的电动车在「广西 - 窃·格瓦拉」面前,锁就是形同虚设,只要他愿意,他就 ...
- 关抢占 自旋锁_互斥锁、自旋锁、读写锁、悲观锁、乐观锁的应用场景
前言 生活中用到的锁,用途都比较简单粗暴,上锁基本是为了防止外人进来.电动车被偷等等. 但生活中也不是没有 BUG 的,比如加锁的电动车在「广西 - 窃·格瓦拉」面前,锁就是形同虚设,只要他愿意,他就 ...
- 面试官:你说说互斥锁、自旋锁、读写锁、悲观锁、乐观锁的应用场景?
前言 生活中用到的锁,用途都比较简单粗暴,上锁基本是为了防止外人进来.电动车被偷等等. 但生活中也不是没有 BUG 的,比如加锁的电动车在「广西 - 窃·格瓦拉」面前,锁就是形同虚设,只要他愿意,他就 ...
- 分布式锁:互斥锁、自旋锁、读写锁、悲观锁、乐观锁
前言 如何用好锁,也是程序员的基本素养之一了. 高并发的场景下,如果选对了合适的锁,则会大大提高系统的性能,否则性能会降低. 所以,知道各种锁的开销,以及应用场景是很有必要的. 接下来,就谈一谈常见的 ...
- 互斥锁、自旋锁、读写锁、悲观锁、乐观锁的应用场景
前言 在编程世界里,「锁」更是五花八门,多种多样,每种锁的加锁开销以及应用场景也可能会不同. 如何用好锁,也是程序员的基本素养之一了. 高并发的场景下,如果选对了合适的锁,则会大大提高系统的性能,否则 ...
- go语言基础-----18-----协程安全、互斥锁、读写锁、匿名锁、sync.Once
1 线(协)程安全-互斥锁 竞态检查工具是基于运行时代码检查,而不是通过代码静态分析来完成的,可以添加-race 来执行竞态检测.但是对于那些没 有机会运行到的代码逻辑中如果存在安全隐患,即使加了-r ...
- java 单例 读写锁_终极锁实战:单JVM锁+分布式锁
目录 1.前言 2.单JVM锁 3.分布式锁 4.总结 =========正文分割线================= 1.前言 锁就像一把钥匙,需要加锁的代码就像一个房间.出现互斥操作的典型场景:多 ...
最新文章
- Linux 启动过程详解
- 关于在Webservice里使用LinqToSQL遇到一对多关系的父子表中子表需要ToList输出泛型而产生循环引用错误的解决办法!(转)...
- 仿京东首页上侧导航左侧地址栏布局(1)
- 开源学习管理系统(LMS)的比较
- 嵌入式Linux系统编程学习之十一Linux进程的创建与控制
- ENVI5.3.1使用Landsat 8影像进行监督分类实例操作
- 运维面试必问的监控系列高频面试题(2021年最新版)
- arcgis批量裁剪影像tif流程_ArcGIS超级工具SPTOOLS-影像的批量裁剪和批量合并
- 电脑计算机c盘缓存清理,怎么清除电脑C盘缓存
- 「Python 网络自动化」Nornir—— Inventory(主机清单)介绍
- Can‘t checkout because of unmerged files You have to resolve all merge conflicts bef
- 三元组损失(Triplet loss)
- mac系统使用指南之色色篇
- php酷狗音乐API接口,酷狗音乐抓取api
- python1 到n_怎么用python求1到n所有整数的和
- 获取MPU9250九轴数据--以四轴飞行器姿态解算为例
- 以太坊柏林升级前的紧急刹车
- 基于图像的人数统计方法
- 超越时代的天才——图灵 1
- ASPX与ASP URL传递值问题