死锁和解决方案

死锁和解决方案

Posted by Lerko on April 12, 2020

死锁的定义

多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进

为什么会死锁

下图是一个死锁的实例示例:

20200412192943

下面是一段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
}

死锁发生的必要条件:

  1. 互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
  2. 请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
  4. 环路等待条件:在发生死锁时,必然存在一个进程–资源的环形链。

其实那个图已经非常清楚了,就是资源处理的时候还互相想要占用对方的锁.

如何防止解决死锁

锁不能进行剥夺

锁不能强制剥夺

请求与保持条件

尽量再资源处理的开头获取全部的锁,并且在结束的时候释放锁

20200412193505

不能有循环等待的条件

不能锁等待时间过长,应该有一个等待的最大值

20200412193708

银行家算法

银行家算法是一个避免死锁的算法

必要规则:

  1. 程序获取资源的最大需求不能超过现有资源的数量
  2. 可以分次获取资源,但是获取的总数量不能大于现有资源
  3. 不满住资源的情况,程序应该进行等待
  4. 其他已经获取资源的程序必须要在一定的时间内归还资源

核心思想是:提前预估需求,资源足够才进行资源配置.

就如同最上面的那个死锁的原因图所示

哲学家就餐问题

20200811085329

假设有五位哲学家围坐在一张圆形餐桌旁,做以下两件事情之一:吃饭,或者思考。吃东西的时候,他们就停止思考,思考的时候也停止吃东西。餐桌中间有一大碗意大利面,每两个哲学家之间有一双筷子。因为用一只筷子很难吃到意大利面,所以假设哲学家必须用两只筷子吃东西。他们只能使用自己左右手边的那两只筷子。

如果出现五个哲学家都只拿到了自己左边的筷子, 那么这个餐桌上就会有一股尴尬的气氛,然后每个哲学家都在等对方放下筷子。 这个时候就更尴尬了,就会进入一个无线循环的等待了,谁也不放。。。

那我们定义一个规则:如果桌面上只有一根筷子的时候,哲学家必须等其他的哲学家吃完,吧筷子放下。当桌面上的筷子数量大于2的时候我们其他的哲学家就可以吃了。 并且每个哲学家必须只能吃特定的一个时间长度,超过这个时间长度的话必须放下筷子。

这样我们就能解决就餐问题了。 这个其实就是临界资源的无规则抢占导致的死锁