Go中缓冲通道的死锁

Posted

技术标签:

【中文标题】Go中缓冲通道的死锁【英文标题】:Deadlocks with buffered channels in Go 【发布时间】:2021-12-25 06:25:13 【问题描述】:

我遇到以下代码fatal error: all goroutines are asleep - deadlock!

我使用缓冲通道是否正确?如果您能给我指点,我将不胜感激。不幸的是,我已经走投无路了。

func main() 
    valueChannel := make(chan int, 2)
    defer close(valueChannel)
    var wg sync.WaitGroup
    for i := 0; i < 10; i++ 
        wg.Add(1)
        go doNothing(&wg, valueChannel)
    

    for 
        v, ok := <- valueChannel
        if !ok 
            break
        
        fmt.Println(v)
    
    wg.Wait()


func doNothing(wg *sync.WaitGroup, numChan chan int) 
    defer wg.Done()
    time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
    numChan <- 12

【问题讨论】:

【参考方案1】:

收到所有值后,&lt;- valueChannel 上的主要 goroutine 阻塞。关闭通道以解除对主 goroutine 的阻塞。

func main() 
    valueChannel := make(chan int, 2)
    var wg sync.WaitGroup
    for i := 0; i < 10; i++ 
        wg.Add(1)
        go doNothing(&wg, valueChannel)
    

    // Close channel after goroutines complete.
    go func() 
        wg.Wait()
        close(valueChannel)
    ()

    // Receive values until channel is closed. 
    // The for / range loop here does the same
    // thing as the for loop in the question.
    for v := range valueChannel 
        fmt.Println(v)
    
 

Run the example on the playground.

上面的代码独立于 goroutines 发送的值的数量。 如果 main() 函数可以确定 goroutines 发送的值的数量,则从 main() 接收该数量的值:

func main() 
    const n = 10

    valueChannel := make(chan int, 2)
    for i := 0; i < n; i++ 
        go doNothing(valueChannel)
    

    // Each call to doNothing sends one value. Receive
    // one value for each call to doNothing.
    for i := 0; i < n; i++ 
        fmt.Println(<-valueChannel)
    


func doNothing(numChan chan int) 
    time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
    numChan <- 12

Run the example on the playground.

【讨论】:

非常感谢。以上工作。但是,为什么 ``` wg.Wait() 和 close(valueChannel) ``` 应​​该在 go 例程中?我试图将它们放在 go 例程之外,但失败了 :( 要中断接收循环,对close(valueChannel) 的调用必须与接收循环同时执行。 Goroutines 用于并发执行代码。 main函数作为Goroutine运行,你可以直接把close放在main里面。 @CeriseLimón @CeriseLimón,那为什么我的代码对我的答案有效? 第3行有:defer close(valueChannel)【参考方案2】:

主要问题在频道接收的for循环上。 逗号 ok 习语在通道上略有不同,ok 表示接收到的值是在通道上发送的(真)还是因为通道关闭且为空而返回的零值(假)。 在这种情况下,通道正在等待发送数据,因为它已经完成了十次发送值:死锁。 因此,除了代码设计之外,如果我只需要在此处进行尽可能少的更改,那就是:

func main() 
    valueChannel := make(chan int, 2)
    defer close(valueChannel)
    var wg sync.WaitGroup
    for i := 0; i < 10; i++ 
        wg.Add(1)
        go doNothing(&wg, valueChannel)
    

    for i := 0; i < 10; i++ 
        v := <- valueChannel

        fmt.Println(v)
    
    wg.Wait()


func doNothing(wg *sync.WaitGroup, numChan chan int) 
    defer wg.Done()
    time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
    numChan <- 12

【讨论】:

以上是关于Go中缓冲通道的死锁的主要内容,如果未能解决你的问题,请参考以下文章

如何获取无缓冲通道中的元素数量

如何解决 Go 通道死锁?

如何在不产生死锁的情况下拥有一个缓冲通道和多个阅读器?

使用通道作为队列的死锁

去通道和死锁

死锁问题+使用通道时增加goroutine的数量