死锁的定义
多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进
为什么会死锁
下图是一个死锁的实例示例:
下面是一段golang的goroutine的死锁实例,因为相互还想争夺锁导致deadlock。 但是这种deadlock是语言本身检查不出来的,需要开发者自己进行排查处理,不然会导致两个goroutine僵死
package main
import (
"fmt"
"sync"
"time"
)
func main() {
deadLock2()
}
func deadLock2() {
a, b := 0, 0
var lockA, lockB sync.Mutex
// 当goroutine1和goroutine2互相尝试获取对方锁这个时候就造成了deadlock
go func() {
for {
fmt.Println("goroutian1 begin")
lockA.Lock()
time.Sleep(100 * time.Microsecond)
a++
lockB.Lock() //尝试获取b的锁
b++
lockB.Unlock()
lockA.Unlock()
fmt.Println("goroutian1", a, b)
}
}()
go func() {
for {
fmt.Println("goroutian2 begin")
lockB.Lock()
time.Sleep(100 * time.Microsecond)
b++
lockA.Lock() //尝试获取A的锁
a++
lockA.Unlock()
lockB.Unlock()
fmt.Println("goroutian2", a, b)
}
}()
time.Sleep(10 * time.Second) //不管这里等待多久goroutine都不会进行输入
fmt.Println(a, b) //最后主goroutine不在进行等待直接输出a b
}
死锁发生的必要条件:
- 互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
- 请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
- 环路等待条件:在发生死锁时,必然存在一个进程–资源的环形链。
其实那个图已经非常清楚了,就是资源处理的时候还互相想要占用对方的锁.
如何防止解决死锁
锁不能进行剥夺
锁不能强制剥夺
请求与保持条件
尽量再资源处理的开头获取全部的锁,并且在结束的时候释放锁
不能有循环等待的条件
不能锁等待时间过长,应该有一个等待的最大值
银行家算法
银行家算法是一个避免死锁的算法
必要规则:
- 程序获取资源的最大需求不能超过现有资源的数量
- 可以分次获取资源,但是获取的总数量不能大于现有资源
- 不满住资源的情况,程序应该进行等待
- 其他已经获取资源的程序必须要在一定的时间内归还资源
核心思想是:提前预估需求,资源足够才进行资源配置.
就如同最上面的那个死锁的原因图所示
哲学家就餐问题
假设有五位哲学家围坐在一张圆形餐桌旁,做以下两件事情之一:吃饭,或者思考。吃东西的时候,他们就停止思考,思考的时候也停止吃东西。餐桌中间有一大碗意大利面,每两个哲学家之间有一双筷子。因为用一只筷子很难吃到意大利面,所以假设哲学家必须用两只筷子吃东西。他们只能使用自己左右手边的那两只筷子。
如果出现五个哲学家都只拿到了自己左边的筷子, 那么这个餐桌上就会有一股尴尬的气氛,然后每个哲学家都在等对方放下筷子。 这个时候就更尴尬了,就会进入一个无线循环的等待了,谁也不放。。。
那我们定义一个规则:如果桌面上只有一根筷子的时候,哲学家必须等其他的哲学家吃完,吧筷子放下。当桌面上的筷子数量大于2的时候我们其他的哲学家就可以吃了。 并且每个哲学家必须只能吃特定的一个时间长度,超过这个时间长度的话必须放下筷子。
这样我们就能解决就餐问题了。 这个其实就是临界资源的无规则抢占导致的死锁