【问题标题】:Why following code generates deadlock为什么下面的代码会产生死锁
【发布时间】:2017-07-09 06:00:43
【问题描述】:

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 步。

【问题讨论】:

  • 您向频道发送了五次消息,但只使用了一次频道。 producer2 goroutine 将被阻塞,直到四个消息的其余部分被消费。
  • 另外,您对sync.WaitGroup 的使用是错误的,因为它调用了Add(5),但只调用了一次Done()
  • @ymonad 我补充了我的理解。你能纠正我哪里出错了吗?注册。 Add(5) - 当消费者执行 5 次时,我期望 Done() 被调用 5 次
  • 在第 3 步中,您提到第 2 步会再次重复,但事实并非如此,您的代码中的消费者只消费一次。

标签: multithreading go channel


【解决方案1】:

你原来的死锁是由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) 添加到生产者函数将修复它。

【讨论】:

    【解决方案2】:

    为什么会出错?

    运行您的代码得到以下错误:

    ➜  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,然后退出
    • producer21 发送到频道,但没有人在消费,因为consumer2 已经退出
    • producer2 正在等待
    • main 执行 wg2.Wait(),但并非所有等待组都已关闭。所以 ma​​in 正在等待

    有两个goroutines在这里等待,什么都不做,不管你等多久都不会做任何事情。 这是一个死锁! Golang 检测到它并恐慌。

    您在这里混淆了两个概念:

    1. waitgourp 的工作原理
    2. 如何从通道接收所有值

    我在这里简单解释一下,网上已经有很多文章了。

    等待组的工作原理

    WaitGroup 如果有一种方法可以等待所有 groutine 完成。在后台运行 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
    

    【讨论】:

      猜你喜欢
      • 2021-12-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多