为什么我的频道需要缓冲区?

时间:2018-07-26 02:54:11

标签: go

我正在尝试学习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的用途。我也用它来创建地图。仅仅是构造函数吗?

2 个答案:

答案 0 :(得分:3)

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

“默认情况下,发送和接收块,直到另一侧准备好为止。” -https://tour.golang.org/concurrency/2

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

由于通道的默认大小为1,因此通道会阻塞,直到接收器从中读取第一个int为止。因此,只有第一个子例程才会写入并退出。其他9个将等待父级清除通道。由于子例程在通道上处于阻塞状态,而父例程在等待上处于阻塞状态,因此您将出现死锁。

缓冲区仅允许通道在阻塞踢之前保留N个其他结果。如果将缓冲区设置得足够大以容纳每个例程的输入,则例程可以退出,从而摆脱了等待状态。

答案 1 :(得分:3)

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

  

默认情况下,发送和接收块,直到另一侧准备好为止。这样一来,goroutine即可进行同步,而无需显式锁定或条件变量。

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

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

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

对于make来说,将其视为构造函数已经足够了,但是知道它仅用于切片,通道和地图。这些是go中的特殊类型,不能像常规结构那样创建。 (我认为这与以下事实有关:每个行为都可以通用,即,它可以容纳任何指定类型的项目,这就是为什么需要make的原因,但是我对此并不是100%的原因。)