死锁的条件与破坏

在多线程编程中,我们为了防止多线程竞争共享资源而导致数据错乱,都会在操作共享资源之前加上互斥锁,只有成功获得到锁的线程,才能操作共享资源,获取不到锁的线程就只能等待,直到锁被释放。

那么,当两个线程为了保护两个不同的共享资源而使用了两个互斥锁,那么这两个互斥锁应用不当的时候,可能会造成两个线程都在等待对方释放锁,在没有外力的作用下,这些线程会一直相互等待,就没办法继续运行,这种情况就是发生了死锁。

举个例子,小林拿了小美房间的钥匙,而小林在自己的房间里,小美拿了小林房间的钥匙,而小美也在自己的房间里。如果小林要从自己的房间里出去,必须拿到小美手中的钥匙,但是小美要出去,又必须拿到小林手中的钥匙,这就形成了死锁。

死锁条件

死锁只有同时满足以下四个条件才会发生:

互斥条件;
持有并等待条件;
不可剥夺条件;
环路等待条件;

互斥条件

互斥条件是指多个线程不能同时使用同一个资源。

比如下图,如果线程 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写个死循环 那也不算死锁 因为一直在干活 没有阻塞住

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐