我正在尝试学习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)
}
}
这是我到目前为止的理解,
但是,我收到一条错误消息:
fatal error: all goroutines are asleep - deadlock!
我不确定这是什么意思。我的猜测是range
试图从通道中获取一个值,但它没有任何值。但这不应该发生,因为我等待所有例程完成,然后关闭通道。
这是怎么回事?
解决方案是执行make(chan int, 10)
之类的操作来为其提供缓冲区,但是我不确定缓冲区是什么还是为什么需要它。
此外,我不确定make
的用途。我也用它来创建地图。仅仅是构造函数吗?
答案 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%的原因。)