Linux产生死锁的必要条件和常见的锁种类
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
目录
前言
- 之前面试的时候有面试官问我产生死锁的(4个)必要条件这个我之前有了解过但是当时觉得这些必要条件很官方也就没有重视最后答得支支吾吾面试官笑着说下去去可以看看这个在面试中跟智能指针一样还挺常见的;面试全程都很顺利卡在这个简单的问题上确实不应该
- 由此面试碰到了让我介绍linux下mutex的种类这个也是之前略知一二但是详细场景和机制还是欠缺;
这篇博客根据上面两个面试中出现的问题以本人个人理解进行总结:
产生死锁的4个必要条件
下面四个必要条件不详细介绍如果不理解需要温习一下mutex互斥量的作用和lock,unlock加锁解锁的底层原理;
1互斥条件
进程要求对所分配的资源进行排它性控制即所分配的资源在一段时间内某资源仅为一进程所占用。
2请求与保持条件
当进程因请求资源而阻塞时对已获得的资源保持不放。(请求新资源的时候保持已获得的旧资源)
3不剥夺条件
进程已获得的资源在未使用完之前不能剥夺只能在使用完时由自己释放。
4循环等待条件
在发生死锁时必然存在一个进程–资源的环形链。
预防(解决)死锁
破坏4个必要条件中的任一个:
互斥这个规则这个为了临界区的安全肯定不能破坏呀;那就破坏后3个;
1资源一次性分配
一次性分配所有资源用完一次性全部释放这样就不会再有别的进程请求时的冲突的现象了
2可剥夺资源
即当某进程获得了部分资源但得不到其它资源则释放(剥夺)已占有的资源破坏不可剥夺条件
3资源有序分配法
系统给每类资源赋予一个编号每一个进程按编号递增的顺序请求资源释放则相反,就不会出现环路了破坏环路等待条件;
Linux常见的锁
我们在开发中常用的锁主要有互斥锁递归锁自旋锁读写锁、乐观锁和悲观锁这五种
互斥锁(普通锁)
- 正常进程间保护临界区的普通锁没有什么特点就是lock()和unlock()的正常上锁解锁;
-
当线程加锁失败时内核会把线程的状态从运行状态设置为睡眠状态然后把CPU切换给其他进程运行
-
当需要的锁被其他线程释放时之前睡眠状态的进程会变为就绪状态然后内核会在合适的时候把CPU切换给该线程运行。
普通锁看似使用简单但是由于内核帮我们切换进程的内核态和用户态存在一定的开销进行了两次线程上下文切换
自旋锁
- 自旋锁在用户态完成加锁和解锁的操作不会主动产生内核态和用户态的切换和上下文的切换所以相比互斥锁来说会快一些开销也小一些。
自旋锁的工作原理是在一段时间内反复尝试获取被占用的锁
当加锁失败时互斥锁用线程切换来应对自旋锁则用忙等待反复尝试获取锁来应对。
互斥锁和自旋锁小结
如果能确定被锁住的代码执行时间很短而且多核处理器就应该使用自旋锁减少了普通mutex线程上下文切换的开销;
如果被锁住的代码执行时间长而且单核处理器还是用普通mutex好点因为这时候递归锁反复长时间轮询检测无疑是浪费了cpu资源的举措。
mutex和自旋锁是锁的两种最基本处理方式更高级的锁都会选择其中一个方式来实现;
比如读写锁既可以选择基于互斥锁实现也可以选择基于自旋锁实现。
递归锁
递归锁只是互斥锁的一个特例同样只能有一个线程访问该对象;
但递归锁允许同一个线程在 未释放其拥有的锁时 反复对 该锁 进行加锁操作普通的锁就死锁了
显然递归锁的常见场景就是对某一个包含加锁操作的递归类型函数进行调用; 某线程反复递归这个函数的时候使用递归锁就不会像普通mutex那样第二次遇到加锁操作就直接进入睡眠状态挂起了;
读写锁
线程间读临界区资源的时候不阻塞只有在有写的时候才起到mutex的作用线程间互相阻塞;
这是一种提高效率的锁比如mysql中的读写锁读的时候因为不会修改临界区的资源不会产生线程安全问题因此读不阻塞效率更高;
写的时候存在线程安全问题那就阻塞了;
乐观锁与悲观锁
悲观锁
前面提到的互斥锁、自旋锁、读写锁都属于悲观锁
悲观锁认为多线程同时对共享资源进行修改的事件发生概率比较高很容易出现冲突问题所以访问共享资源前必须先上锁。做法有点谨慎悲观因此得名
乐观锁(无锁编程)
- 与悲观锁相反乐观锁认为冲突的概率很低;
它的工作方式是先修改完共享资源再验证这段时间内有没有发生冲突
- 如果没有其他线程在修改资源那么操作完成;(赚了没发生冲突而且执行效率还高)
- 如果发现有其他线程已经修改过这个资源就直接无脑放弃本次操作。(不亏有冲突放弃这次操作以免事态恶化之后进行重试 做法很乐观因此得名)
乐观锁全程没有加锁所以它也叫无锁编程
乐观锁和悲观锁小结
悲观锁直接进行加锁操作进行加锁的操作适用于冲突事件发生概率高的场景(这个场景用悲观锁得不停地)
乐观锁虽然去除了加锁解锁的操作但是一旦发生冲突重试的成本其实也非常高所以只有在冲突事件发生的概率非常低且加锁成本非常高的场景(比如cpu执行效率非常快)时才考虑使用乐观锁。
其他锁(了解)
还有很多锁比如mysql在RR隔离级别下处理幻读问题使用了row行锁+GAP间隙锁等都是一些高级的适用于某种场景的锁所以锁的可拓展性还是很大的;