为什么以下代码会生成死锁

时间:2017-02-20 04:16:28

标签: multithreading go channel

Golang新手在这里。有人可以解释为什么以下代码会产生死锁吗?

我知道发送true到boolean< - 已完成的频道,但我不想使用它。

package main

import (
    "fmt"
    "sync"
    "time"
)

var wg2 sync.WaitGroup

func producer2(c chan<- int) {
    for i := 0; i < 5; i++ {
        time.Sleep(time.Second * 10)
        fmt.Println("Producer Writing to chan %d", i)
        c <- i
    }
}

func consumer2(c <-chan int) {
    defer wg2.Done()
    fmt.Println("Consumer Got value %d", <-c)

}

func main() {
    c := make(chan int)
    wg2.Add(5)
    fmt.Println("Starting .... 1")
    go producer2(c)
    go consumer2(c)
    fmt.Println("Starting .... 2")

    wg2.Wait()
}

以下是我的理解,我知道这是错误的:

  1. 在0内写入通道时,通道将被阻止 生产者函数循环
  2. 所以我希望通道被清空 消费者事后。
  3. 由于在步骤2中清空了频道, 生产者函数可以再次放入另一个值然后得到 阻止,步骤2再次重复。

2 个答案:

答案 0 :(得分:3)

你原来的死锁是由wg2.Add(5)引起的,你等待5个goroutines完成,但只有一个做了;你打电话给wg2.Done()一次。将其更改为wg2.Add(1),您的程序将正常运行。

但是,我怀疑你打算消耗频道中的所有值,而不是像你一样。如果您将消费者功能更改为:

func consumer2(c <-chan int) {
    defer wg2.Done()
    for i := range c {
        fmt.Printf("Consumer Got value %d\n", i)
    }
}

您将获得另一个死锁,因为生产者函数中的通道未关闭,并且消费者正在等待从未到达的更多值。将close(c)添加到生成器函数将修复它。

答案 1 :(得分:1)

为什么会出错?

运行代码会出现以下错误:

➜  gochannel go run dl.go
Starting .... 1
Starting .... 2
Producer Writing to chan 0
Consumer Got value 0
Producer Writing to chan 1
fatal error: all goroutines are asleep - deadlock!

原因如下:

您的代码中有三个goroutine:mainproducer2consumer2。当它运行时,

  • producer2向频道
  • 发送一个号码0
  • consumer2从频道中重温0退出
  • producer2向频道发送1,但没有人消费,因为consumer2已退出
  • producer2 等待
  • main执行wg2.Wait(),但并非所有waitgroup都已关闭。所以主要等待

两个goroutine在这里等着,什么都不做,无论你等多久都没有做任何事情。 这是一个僵局! Golang检测到它并发生恐慌。

这里有两个你困惑的概念:

  1. waitgourp如何运作
  2. 如何从频道接收所有值
  3. 我将在这里简要解释一下,互联网上有很多文章。

    waitgroup如何工作

    WaitGroup如果有办法等待所有的灌浆完成。在后台运行goroutine时,重要的是要知道它们何时退出,然后才能执行某些操作。

    在你的情况下,我们运行两个goroutine,所以在开始时我们应该设置wg2.Add(2),每个goroutine应该添加wg2.Done()以通知它已完成。

    从频道接收数据

    从频道接收数据时。如果您确切知道它将发送多少数据,请以这种方式循环使用for

    for i:=0; i<N; i++ {
        data = <-c
        process(data)
    }
    

    否则以这种方式使用它:

    for data := range c {
        process(data)
    }
    

    此外,当没有更多数据要发送时,别忘了关闭频道。

    如何解决?

    通过上述说明,代码可以固定为:

    package main
    
    import (
        "fmt"
        "sync"
        "time"
    )
    
    var wg2 sync.WaitGroup
    
    func producer2(c chan<- int) {
        defer wg2.Done()
        for i := 0; i < 5; i++ {
            time.Sleep(time.Second * 1)
            fmt.Printf("Producer Writing to chan %d\n", i)
            c <- i
        }
        close(c)
    }
    
    func consumer2(c <-chan int) {
        defer wg2.Done()
        for i := range c {
            fmt.Printf("Consumer Got value %d\n", i)
        }
    
    }
    
    func main() {
        c := make(chan int)
        wg2.Add(2)
        fmt.Println("Starting .... 1")
        go producer2(c)
        go consumer2(c)
        fmt.Println("Starting .... 2")
    
        wg2.Wait()
    }
    

    Here是解决此问题的另一种方法。

    预期输出

    固定代码提供以下输出:

    ➜  gochannel go run dl.go
    Starting .... 1
    Starting .... 2
    Producer Writing to chan 0
    Consumer Got value 0
    Producer Writing to chan 1
    Consumer Got value 1
    Producer Writing to chan 2
    Consumer Got value 2
    Producer Writing to chan 3
    Consumer Got value 3
    Producer Writing to chan 4
    Consumer Got value 4