我有一段代码,我试图根据close
调用和位置来理解它
func main() {
ch := make(chan int, 2)
go func(ch chan int) {
for i := 1; i <= 5; i++ {
ch <- i
fmt.Println("Func goroutine sends data: ", i)
}
//Pos1 - Works perfectly
//close(ch)
}(ch)
fmt.Println("Main goroutine sleeps 2 seconds")
time.Sleep(time.Second * 2)
fmt.Println("Main goroutine begins receiving data")
//Pos2 - Puts in only 2 ints 1 and 2 and then prints only that
//close(ch)
for d := range ch {
fmt.Println("Main goroutine received data:", d)
}
//Pos3 - Throws fatal error
close(ch)
}
我一直在努力理解和阅读这方面的博客,但仍然不能理解一些事情
1.当我把close放在Pos1时,它工作得很好。但是我不知道为什么它工作。缓冲区在任何给定的时间不能容纳超过2个元素,所以当2个元素被写入时,循环将阻塞,直到主路由进行读取。但是我想在缓冲通道上做一个范围,range函数必须事先知道要迭代多少个元素,并且对于该通道必须关闭。为什么close
在这个位置工作?
1.当我把它放在位置2时,它只打印了2个元素,这是有意义的,但为什么for loop
在试图向关闭的通道写入更多元素时没有抛出异常?
1.当我在Pos3结束时,我得到了一个异常fatal error: all goroutines are asleep - deadlock!
,尽管所有5个int都被打印出来了。为什么?
2条答案
按热度按时间hs1ihplo1#
但我认为要在缓冲通道上执行范围,范围函数必须事先知道要迭代多少个元素,并且必须关闭该通道。
这种假设是错误的,是所有误解的根源。
通道范围的行为在Go Spec:https://golang.org/ref/spec#For_statements
对于通道,产生的迭代值是在通道上发送的连续值,直到通道关闭。如果通道为nil,范围表达式将永远阻塞。
在计算for语句时不需要关闭通道,并且该语句不需要知道元素的数量。
所以,在你的代码中,当你把
close
放在Pos 1时,这确实是正确的方法;当你把它放在Pos 3时,for循环等待通道关闭,这只能在for循环本身之后发生,所以这是一个死锁。把
close
放在Pos 2中是有缺陷的,而且行为有点棘手。它 * 可能 * 引发错误,但也可能只输出两个数字。这是因为当通道在for循环之前关闭时,循环可以无阻塞地运行,然后main()
返回。当main()
返回时,Go程序结束,是否会引发错误完全取决于进程间的调度器是否切换到goroutine,但这并不能保证。weylhg0b2#
修改代码片段,添加一些peppered print语句到@leaf bebop的优秀答案中,以澄清发生了什么。
{1}{2}位置{3} 2 {4},但让主电源稍等{5}{6}
因此,如果有足够的时间(主例程没有退出),调度程序返回到go例程,在那里我们写入一个关闭的(由主例程关闭的)通道- Panic!
位置2,但仅发送2个值,即不写入关闭的通道
没有恐慌,永远没有旋转。主程序只是退出。
{1}{2}位置{3} 3 {4},检查总管是否畅通?{5}{6}
Main甚至没有关闭通道,它在range循环中被阻塞了。它将永远停留在这里,直到它得到一个值,或者有人关闭了通道。我们没有任何其他例程来做这件事,所以死锁。