为啥在使用等待组和通道时会出现死锁?

Posted

技术标签:

【中文标题】为啥在使用等待组和通道时会出现死锁?【英文标题】:why is there deadlock when using wait group and channel in go?为什么在使用等待组和通道时会出现死锁? 【发布时间】:2022-01-10 10:46:47 【问题描述】:

我想使用 setter 函数将 0-9 发送到 ch1 通道,然后计算机函数将 ch1 中的数字平方/em>,然后将结果发送到 ch2 频道。但是,运行此代码时我会感到恐慌。谁能解释一下为什么会发生这种情况,我完全糊涂了。

package main

import (
    "fmt"
    "sync"
)

func setter(ch1 chan int, wg sync.WaitGroup) 
    for i:= 0; i< 10;i++ 
        fmt.Println("setter:", i)
        ch1 <- i
    
    close(ch1)
    wg.Done()


func computer(ch1 chan int, ch2 chan int, wg sync.WaitGroup) 
    for true 
        tmp, ok := <- ch1
        if !ok 
            fmt.Println("computer: there is no value in ch1")
            break
        
        fmt.Println("computer:", tmp*tmp)
        ch2 <- tmp*tmp
    
    close(ch2)
    wg.Done()


func main()
    ch1 := make(chan int,1)
    ch2 := make(chan int,1)
    var wg sync.WaitGroup
    wg.Add(2)
    go setter(ch1, wg)
    go computer(ch1, ch2, wg)

    wg.Wait()

这样的错误:

setter: 0
setter: 1
setter: 2
computer: 0
computer: 1
setter: 3
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [semacquire]:
sync.runtime_Semacquire(0xc000196008)
        /usr/local/go/src/runtime/sema.go:56 +0x45
sync.(*WaitGroup).Wait(0xc000196000)
        /usr/local/go/src/sync/waitgroup.go:130 +0x65
main.main()
        /Users/koujianyuan/Desktop/code/project/go/chan/communication/demo.go:50 +0x13b

goroutine 18 [chan send]:
main.setter(0xc000194000, 0x200000000, 0xc000000000)
        /Users/koujianyuan/Desktop/code/project/go/chan/communication/demo.go:16 +0x107
created by main.main
        /Users/koujianyuan/Desktop/code/project/go/chan/communication/demo.go:47 +0xdb

goroutine 19 [chan send]:
main.computer(0xc000194000, 0xc000194070, 0x200000000, 0x0)
        /Users/koujianyuan/Desktop/code/project/go/chan/communication/demo.go:35 +0x11c
created by main.main
        /Users/koujianyuan/Desktop/code/project/go/chan/communication/demo.go:48 +0x12d

【问题讨论】:

在两个函数中使用 wg *sync.WaitGroup 并将 &amp;wg 传递给 goroutines。 您没有从任何地方读取ch2,因此当您将第二个值放入其中时它会阻塞。 【参考方案1】:

出现死锁的原因是你没有从ch2频道读取。 ch2 通道块,当您第二次尝试将值放入其中时(第一次通过,因为它是 1 的缓冲通道)。当将值放入ch2 通道块时,它也会阻止从ch1 通道中读取值,因此setter goroutine 不能再将值放入ch1

ch1ch2 通道都被阻塞,settercomputer 协程都无法完成,从而导致死锁。

Here 是一个工作示例。我添加了从ch2 频道读取的reader 函数。

// The rest of the code is the same except I've changed the functions to use *sync.Waitgroup

func reader(ch chan int, wg *sync.WaitGroup) 
    defer wg.Done()
    for i := range ch 
        fmt.Println("reading from channel", i)
    
    fmt.Println("reader exiting")


func main() 
    ch1 := make(chan int, 1)
    ch2 := make(chan int, 1)
    var wg sync.WaitGroup
    wg.Add(3)
    go reader(ch2, &wg)
    go setter(ch1, &wg)
    go computer(ch1, ch2, &wg)

    wg.Wait()

【讨论】:

以上是关于为啥在使用等待组和通道时会出现死锁?的主要内容,如果未能解决你的问题,请参考以下文章

为啥在全局范围内声明通道会产生死锁问题

为啥立体声 mp3 文件在使用 ffmpeg 从 mp4 转换时会丢失一个通道?

为啥当我运行快速通道快照时会得到`** TEST FAILED **`?

为啥我在录屏时会出现无声?

为啥与多个 Popen 子进程一起使用时会出现通信死锁?

与通道和 goroutine 同步