page contents

golang什么是死锁 - 关于使用 channel 时遇到的死锁问题

1.发生死锁的代码 func deadlockTest() { ch := make(chan int) results := make(chan int) for i := 0; i < 2; i++ { go func() { // 把从channel里...

attachments-2021-05-o0gnouJl60af12f823df8.png

1.发生死锁的代码

func deadlockTest() {

    ch := make(chan int)
    results := make(chan int)

    for i := 0; i < 2; i++ {
        go func() {
            // 把从channel里取得的数据,再传回去
            x := <-ch
            results <- x

        }()
    }

    // 向输入数据里传两个数据
    ch <- 1
    ch <- 2

    for re := range results {
        fmt.Printf("re:%v\n", re)
    }

}
结果:

re:1
re:2
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:
main.deadlockTest()
    /Users/iss/goproj/go_bible/src/main.go:45 +0x174
main.main()
    /Users/iss/goproj/go_bible/src/main.go:14 +0x20

Process finished with exit code 0

2. 发生死锁的原因

主程序中的for re := range results代码,在处理完1和2两条数据后,还一直在等待 results channel 的内容,无法结束。这样的话,Go就会判定产生了死锁

for re := range results {
        fmt.Printf("re:%v\n", re)
    }

3. 如果解决这个死锁问题

方案1:
代码

func deadlockTestOKVer1() {

    ch := make(chan int)
    results := make(chan int)

    for i := 0; i < 2; i++ {
        go func() {
            // 把从channel里取得的数据,再传回去
            x := <-ch
            results <- x

        }()
    }

    // 向输入数据里传两个数据
    ch <- 1
    ch <- 2

    go func() {
        for re := range results {
            fmt.Printf("re:%v\n", re)
        }
    }()

    // 等待3秒后,才结束主程序。
    // 如果不加等待的话,上面的 go func 可能还没有执行,程序就结束了,go func 跟着结束了。
    time.Sleep(3 * time.Second)

}

解析:
1,这个方法是把 results channel 的接收处理,放到一个goroutine里去做。这样的话,主程序就不会卡住不动,即产生死锁了。
2,然后,在最后一行加了一个等待(time.Sleep(3 * time.Second))。如果不加等待的话,上面的 go func 可能还没有执行,程序就结束了,go func 跟着结束了。(和Sleep相比,还有更好的方法,比如使用WaitGroup)

(fatal error: all goroutines are asleep - deadlock!)这种抛出 error 的死锁,是只有主程序卡住不动才会生产的吗?如果是的话,那不让主程序卡住不动,就不会发生这种事情了。所以以下的各种解决方案,都是围绕如何“不让主程序卡住”来做的。

方法2:
代码

func deadlockTestOKVer2() {

    var wg sync.WaitGroup
    ch := make(chan int)
    results := make(chan int)

    for i := 0; i < 2; i++ {
        wg.Add(1)
        go func() {
            // 把从channel里取得的数据,再传回去
            x := <-ch
            results <- x

            defer wg.Done()

        }()
    }

    // 在(for i := 0; i < 2; i++)循环里的 go func 都执行完后,
    // 关闭 result channel,这样主程序里的 for reuslts 循环 就可以正常结束了。
    go func() {
        wg.Wait()
        close(results)
    }()

    // 向输入数据里传两个数据
    ch <- 1
    ch <- 2

    for re := range results {
        fmt.Printf("re:%v\n", re)
    }

    // 等待3秒后,才结束主程序。
    // 如果不加等待的话,上面的 go func 可能还没有执行,程序就结束了,go func 跟着结束了。
    time.Sleep(3 * time.Second)

}

解析
最上面死锁的原因是,for result循环一直在等待输入,但实际上输入只有2个,处理完后就没有了。为了解决这个问题,把 results channel 关闭,for result循环就可以正常退出了。
具体细节是,在所有的 results channel 的输入处理之前,wg.Wait()这个goroutine会处于等待状态。当处理完后(wg.Done),wg.Wait()就会放开执行,执行后面的close(results)。

注意:要把wg.Add(1)放到go func外面。如果放到里面的话,(for i := 0; i < 2; i++)里的go func有可能会在wg.wait的go func之后执行,这样close(results)就会先执行。

4. 什么样的情况会死锁?

总结一下,channel 上如果发生了流入和流出不配对,就可能会发生死锁。
例子:

(1)无缓冲区死锁
func deadlockTest1() {
    ch := make(chan int)
    ch <- 0
}

(2)有缓冲区死锁
func deadlockTest1() {
    ch := make(chan int, 1)
    ch <- 0
    <- ch
    <- ch
}

如何不发生死锁
1,明确保证流入和流出成对出现
2,当无法保证流入和流出成对出现时(例如上面的 for range),在流入完成后,关闭 channel。(上面的方法2)
3,利用缓冲。明确保证流入成对出现的话,可以使用缓冲来解决流入的阻塞。

func main() {
    ch := make(chan int, 3)
    ch <- 1
    ch <- 2
    ch <- 3

    for v := range ch {
        fmt.Println(v)
    }
}

更多相关技术内容咨询欢迎前往并持续关注六星社区了解详情。

如果你想用Python开辟副业赚钱,但不熟悉爬虫与反爬虫技术,没有接单途径,也缺乏兼职经验
关注下方微信公众号:Python编程学习圈,获取价值999元全套Python入门到进阶的学习资料以及教程,还有Python技术交流群一起交流学习哦。

attachments-2022-06-BhOTRWwV62ac197b478e0.jpeg

  • 发表于 2021-05-27 11:33
  • 阅读 ( 492 )
  • 分类:Golang

0 条评论

请先 登录 后评论
轩辕小不懂
轩辕小不懂

2403 篇文章

作家榜 »

  1. 轩辕小不懂 2403 文章
  2. 小柒 1324 文章
  3. Pack 1135 文章
  4. Nen 576 文章
  5. 王昭君 209 文章
  6. 文双 71 文章
  7. 小威 64 文章
  8. Cara 36 文章