死锁到底是什么?如何破除死锁 以及和永久阻塞的区别在哪?
而这个是永久阻塞 ,虽然主协程永久阻塞了 分协程还在干活而如果有分协程向done发送了数据 则就可以退出阻塞。死锁是:所有的还没结束运行的groutine都陷入了永久阻塞状态外力无法把它们拉出来。区分的意义是:如果只是永久阻塞 只会内存泄露为了区分内存泄露和死锁 我们要区分开。永久阻塞是:多个groutine,只有一个进入了阻塞状态 其他还是正常执行的。需要特别指出的是 main方法结束 所有协程
死锁的条件与破坏
在多线程编程中,我们为了防止多线程竞争共享资源而导致数据错乱,都会在操作共享资源之前加上互斥锁,只有成功获得到锁的线程,才能操作共享资源,获取不到锁的线程就只能等待,直到锁被释放。
那么,当两个线程为了保护两个不同的共享资源而使用了两个互斥锁,那么这两个互斥锁应用不当的时候,可能会造成两个线程都在等待对方释放锁,在没有外力的作用下,这些线程会一直相互等待,就没办法继续运行,这种情况就是发生了死锁。
举个例子,小林拿了小美房间的钥匙,而小林在自己的房间里,小美拿了小林房间的钥匙,而小美也在自己的房间里。如果小林要从自己的房间里出去,必须拿到小美手中的钥匙,但是小美要出去,又必须拿到小林手中的钥匙,这就形成了死锁。
死锁条件
死锁只有同时满足以下四个条件才会发生:
互斥条件;
持有并等待条件;
不可剥夺条件;
环路等待条件;
互斥条件
互斥条件是指多个线程不能同时使用同一个资源。
比如下图,如果线程 A 已经持有的资源,不能再同时被线程 B 持有,如果线程 B 请求获取线程 A 已经占用的资源,那线程 B 只能等待,直到线程 A 释放了资源。
持有并等待条件
持有并等待条件是指,当线程 A 已经持有了资源 1,又想申请资源 2,而资源 2 已经被线程 C 持有了,所以线程 A 就会处于等待状态,但是线程 A 在等待资源 2 的同时并不会释放自己已经持有的资源 。
不可剥夺条件
不可剥夺条件是指,当线程已经持有了资源 ,在自己使用完之前不能被其他线程获取,线程 B 如果也想使用此资源,则只能在线程 A 使用完并释放后才能获取。
环路等待条件
环路等待条件指的是,在死锁发生的时候,两个线程获取资源的顺序构成了环形链。
比如,线程 A 已经持有资源 2,而想请求资源 1, 线程 B 已经获取了资源 1,而想请求资源 2,这就形成资源请求等待的环形图。
如何破坏死锁
前面我们提到,产生死锁的四个必要条件是:互斥条件、持有并等待条件、不可剥夺条件、环路等待条件。
那么避免死锁问题就只需要破环其中一个条件就可以,最常见的并且可行的就是使用资源有序分配法,来破环环路等待条件。
那什么是资源有序分配法呢?
线程 A 和 线程 B 获取资源的顺序要一样,当线程 A 是先尝试获取资源 A,然后尝试获取资源 B 的时候,线程 B 同样也是先尝试获取资源 A,然后尝试获取资源 B。也就是说,线程 A 和 线程 B 总是以相同的顺序申请自己想要的资源。
死锁和永久阻塞的区别
最大区别 永久阻塞可能是单个进程无线等待
而死锁需要两个进程因为资源竞争 而互相等待对方 从而一个都无法执行!
永久阻塞是:多个groutine,只有一个进入了阻塞状态 其他还是正常执行的
死锁是:所有的还没结束运行的groutine都陷入了永久阻塞状态 外力无法把它们拉出来
需要特别指出的是 main方法结束 所有协程也会结束 不管是不是永久阻塞状态
区分的意义是:如果只是永久阻塞 只会内存泄露 为了区分内存泄露和死锁 我们要区分开
比如这个例子是死锁
package main
func main() {
ch := make(chan int)
ch <- 1
}
报错是:fatal error: all goroutines are asleep - deadlock!
这个错误是运行时报错 不是编译的时候就报错
比如
package main
import "fmt"
func main() {
ch := make(chan int)
fmt.Println(1)
ch <- 1
}
会先输出1再报错死锁
而这个是永久阻塞 ,虽然主协程永久阻塞了 分协程还在干活 而如果有分协程向done发送了数据 则就可以退出阻塞
package main
import (
"fmt"
"time"
)
func main() {
done := make(chan bool) // 创建一个通道,但不发送或接收任何值
go func() {
for {
time.Sleep(5 * time.Second) // 每5秒打印一次
fmt.Println("Tick")
}
}()
<-done // 这个永远不会发生,因为done通道没有被关闭或发送任何值,所以main函数会无限期地等待,程序不会退出。
//其他代码流程
}
下面这个也不是死锁
package main
func main() {
done := make(chan bool) // 创建一个通道,但不发送或接收任何值
go func() {
for {
}
}()
<-done // 这个永远不会发生,因为done通道没有被关闭或发送任何值,所以main函数会无限期地等待,程序不会退出。
}
但是这个是死锁 因为分协程退出了
package main
func main() {
done := make(chan bool) // 创建一个通道,但不发送或接收任何值
go func() {
}()
<-done // 这个永远不会发生,因为done通道没有被关闭或发送任何值,所以main函数会无限期地等待,程序不会退出。
}
如果你在main写个死循环 那也不算死锁 因为一直在干活 没有阻塞住
更多推荐
所有评论(0)