【问题标题】:Why do I need a buffer for my channel?为什么我的频道需要缓冲区?
【发布时间】:2019-01-02 22:44:00
【问题描述】:

我正在尝试学习 Go,我正在使用 this tutorial

我已经写了以下代码,

var wg sync.WaitGroup

func foo(c chan int, someValue int) {
    defer wg.Done()
    c <- someValue * 5
}

func main() {
    fooVal := make(chan int)
    for i := 0; i < 10; i++ {
        go foo(fooVal, i)
        wg.Add(1)
    }

    wg.Wait() // Wait for all routines to complete
    close(fooVal) // close channel

    for item := range fooVal {
        fmt.Println(item)
    }
}

这是我目前的理解,

  • 我创建了一个接收整数的通道
  • 我创建了 10 个子例程,并将 1 个添加到等待组中,以便稍后同步它们
  • 我等待例程完成
  • 我关闭了通道,使其不再接收任何值
  • 我循环遍历通道中的值以打印它们

但是,我收到一条错误消息:

fatal error: all goroutines are asleep - deadlock!

我不确定这意味着什么。我的猜测是 range 试图从通道中获取一个值,但它没有任何值。但这不应该发生,因为我等待所有例程完成,然后我关闭了频道。

发生了什么事?

对此的解决方案是执行make(chan int, 10) 之类的操作来为其提供缓冲区,但我不确定缓冲区是什么或为什么需要它。

另外,我不确定make 做了什么。我也用它来创建地图。它只是一个构造函数吗?

【问题讨论】:

  • 旁白:你必须调用 wg.Add before 启动 goroutine,否则 Done 可能会在 Add 和恐慌之前被调用:play.golang.org/p/X1cK7Lcj_K4

标签: go


【解决方案1】:

你所有的 goroutine 都处于休眠状态的原因是,默认情况下,通道会阻塞它的 goroutine 发送,直到接收到值。 见https://tour.golang.org/concurrency/2

默认情况下,发送和接收块,直到对方准备好。这允许 goroutines 在没有显式锁或条件变量的情况下进行同步。

缓冲区通过允许通道在不阻塞的情况下“保存”这么多值来解决此问题。将缓冲通道视为可以容纳 N 个项目的存储桶(其中 N 是缓冲区大小,在您的情况下为 10),以便将更多内容放入存储桶中,您需要等待某些内容被删除(即读取)。默认情况下,通道是无缓冲的,必须先接收到它的值,然后才能解除对发送 goroutine 的阻塞。

在您的代码中,您有 goroutines 试图将项目放入您的无缓冲通道,因此第一个例程等待它放入通道的项目被读取。但是在 range 语句读取它之前,您的通道永远不会清空,直到通道清空并且 goroutine 完成(因为 wg.Wait())才会触发,这是一个死锁。两者都无法继续。

使用缓冲区,缓冲通道告诉您的 goroutine 可以在不读取其值的情况下完成(在您的情况下最多 10 个项目),因此等待组完成,并且 range 语句按预期读取所有值。

至于make,把它当作一个构造函数就足够了,但要知道它只用于切片、通道和地图。这些是 go 中的特殊类型,不能像常规结构一样创建。 (我认为这与每个行为都具有通用性有关,即它可以容纳任何指定类型的项目,这就是需要 make 的原因,但我不是 100% 的)。

【讨论】:

    【解决方案2】:

    这是对渠道运作方式的一个相当简单的误解。

    “默认情况下,在对方准备好之前发送和接收块。” - https://tour.golang.org/concurrency/2

    您已经创建了一个通道来接收来自您的 goroutine 的所有结果,并且您已经创建了 10 个例程。在您的创建循环之后,您立即等待所有例程退出......但父级没有尝试从通道中读取数据

    由于通道的默认大小为 1,因此通道会阻塞,直到接收器从中读取第一个 int。所以只有第一个子程序会写入和退出。其他 9 个将等待父母清除通道。由于子程序在通道上阻塞,而父程序在等待时阻塞,所以你有一个死锁。

    缓冲区只是允许通道在阻塞开始之前再保存 N 个结果。如果您使缓冲区足够大以容纳来自每个例程的输入,那么例程就能够退出,并且您摆脱了等待条件。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-12-09
      • 1970-01-01
      • 2016-08-02
      相关资源
      最近更新 更多