死锁
死锁是指由于两个或多个进程(线程同样)互相持有对方所需要的资源,导致这些线程处于等待状态(阻塞现象),没有外力干扰的情况下无法继续执行。互相等待对方释放资源,产生死锁。
产生死锁的必要条件(4个)
1. 资源互斥条件: 进程对于所分配到的资源具有排他性,一个资源只能由一个进程占用,知道该进程释放该资源。
2. 请求保持条件: 进程会请求所有需要的资源,如果过程中因为缺少资源而阻塞,已请求到的资源不会释放。
3. 不可剥夺条件: 进程无法剥夺其他进程的资源,知道其他进程释放资源后才可能请求到。
4. 循环等待条件: 死锁发生时,一定有两个或以上的进程形成环路,在等待下一个进程的某些资源,导致永久阻塞。
这四个条件是死锁产生的必要条件,只要不满足所有四个条件,就不会出现死锁。
举个栗子,两个goroutine各申请两个锁,各占有一个锁的情况下申请另一个导致阻塞。
package main import ( "fmt" "sync" "time" ) var l1 sync.Mutex var l2 sync.Mutex func main() { go f1() go f2() time.Sleep(5 * time.Second) fmt.Println("end") } func f1() { l1.Lock() fmt.Println("f1 -- l1 is locking") time.Sleep(2 * time.Second) l2.Lock() fmt.Println("f1 -- l2 locking") time.Sleep(2 * time.Second) l2.Unlock() fmt.Println("f1 -- l2 unlock") l1.Unlock() fmt.Println("f1 -- l1 unlock") } func f2() { l2.Lock() fmt.Println("f2 -- l2 is locking") time.Sleep(2 * time.Second) l1.Lock() fmt.Println("f2 -- l1 locking") time.Sleep(2 * time.Second) l1.Unlock() fmt.Println("f2 -- l1 unlock") l2.Unlock() fmt.Println("f2 -- l2 unlock") } Print: f1 -- l1 is locking f2 -- l2 is locking end
下面是比较容易混淆的概念(面试题中,有时候会搞混)
预防死锁(确保系统不会进入死锁状态)
死锁预防,只要能做到不让上述四个条件全部达成就可以。其中互斥条件最好不要动。
1.破坏请求保持 进程开始运行前,一次性申请全部资源。优势:简单易用安全。劣势:资源利用率下降。
2.破坏不可剥夺 进程请求资源过程中,遇到无法获得的资源,释放所有已获得的资源。 劣势:实现复杂,增加系统开销。
3.破坏循环等待 对系统资源进行排序(顺序分配资源法) 劣势:资源浪费,不易实现。
避免死锁(判断,只允许不产生死锁的进程申请资源)
避免死锁比预防死锁的限制小,但是必须事先声明每个进程的最大资源量,分配资源数目是固定的。
1. 一次封锁法: 一次分配所有资源
2. 顺序封锁法: 固定顺序分配资源。 劣势:必须实现知道所有可能用到的锁,并且要知道获取顺序。
3. 加锁时限法: 设置时钟,到达时钟仍然无法获得资源就释放资源。 劣势:资源浪费,甚至可能系统情况越来越糟。
3. 银行家算法(常用)
检测死锁
这部分是根据现有进程的所需资源画图进行分配预演。
根据进程画出资源分配图,简化资源分配图,使用死锁定理判断。
死锁定理: 系统没有环路,则没有死锁,否则,可能存在死锁。
解除死锁
1. 抢占资源: 从一个或多个进程中抢占足够数量的资源分配给死锁进程,以解除死锁状态。这时可能挂起某些死锁进程,将这些资源分配给其他的死锁进程。但应防止被挂起的进程长时间得不到资源,而处于资源匮乏的状态。
2. 撤销进程: 强制撤销部分、甚至全部死锁进程并剥夺这些进程的资源。撤销的原则可以按进程优先级和撤销进程代价的高低进行。
3. 进程回退: 让一(多)个进程回退到足以回避死锁的地步,进程回退时自愿释放资源而不是被剥夺。要求系统保持进程的历史信息,设置还原点。
记录每天解决的小问题,积累起来去解决大问题
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Lazyboy_/article/details/89458125