英文:
Deadlock error in GoLang tutorial
问题
我正在尝试完成这个教程 -
https://tour.golang.org/concurrency/8
这是我的代码
// Walk函数遍历树t,并将所有的值发送到通道ch。
func Walk(t *tree.Tree, ch chan int) {
if t != nil {
Walk(t.Left, ch)
ch <- t.Value
Walk(t.Right,ch)
}
}
// Same函数判断树t1和t2是否包含相同的值。
func Same(t1, t2 *tree.Tree) bool {
ch1 := make(chan int)
ch2 := make(chan int)
go Walk(t1, ch1)
go Walk(t2, ch2)
for i := range ch1 {
if i != <-ch2 {
return false
}
}
return true
}
func main() {
isSame := Same(tree.New(1), tree.New(1))
if isSame {
fmt.Println("SAME")
} else {
fmt.Println("DIFF")
}
}
但是我得到了这个错误-
fatal error: all goroutines are asleep - deadlock!
它曾经工作过,然后我再次运行它,它就停止工作了...要么是那样,要么是我疯了。
发生了什么?
英文:
I'm trying to do this tutorial -
https://tour.golang.org/concurrency/8
This is my code
// Walk walks the tree t sending all values
// from the tree to the channel ch.
func Walk(t *tree.Tree, ch chan int) {
if t != nil {
Walk(t.Left, ch)
ch <- t.Value
Walk(t.Right,ch)
}
}
// Same determines whether the trees
// t1 and t2 contain the same values.
func Same(t1, t2 *tree.Tree) bool {
ch1 := make(chan int)
ch2 := make(chan int)
go Walk(t1, ch1)
go Walk(t2, ch2)
for i:= range ch1 {
if i != <-ch2 {
return false
}
}
return true
}
func main() {
isSame := Same(tree.New(1), tree.New(1))
if isSame {
fmt.Println("SAME")
} else {
fmt.Println("DIFF")
}
}
But I get this error-
fatal error: all goroutines are asleep - deadlock!
It worked once, and then I ran it again and it stopped working...either that or I'm crazy.
What's going on?
答案1
得分: 3
问题在于你从未关闭 ch1
,所以你的 for i:= range ch1
循环永远不会结束;它只会读取直到通道中没有值,然后阻塞。此时,只会有一个 goroutine,并且它被阻塞在监听空通道上,因此 Go 会中止并显示你看到的消息。(类似地,你从未关闭 ch2
,但在你的情况下这并不重要。如果 ch2
的值少于 ch1
,这将导致死锁。)
老实说,我不确定“Go 之旅”团队究竟想要什么解决方案。
一种可行但完全作弊的选项是硬编码你只会看到十个值的事实:
for i := 0; i < 10; i++ {
if <-ch1 != <-ch2 {
return false
}
}
更好的选择是让 Same
使用匿名函数来调用 Walk
,然后关闭通道:
go func() {
Walk(t1, ch1)
close(ch1)
}()
(或者你可以使用非匿名函数;将其命名为 walkAndClose
或其他名称)。
顺便说一下,你的 Same
函数假设两棵树具有相同的大小。如果 t1
的元素更多,那么 t2
将在末尾隐式填充零(因为一旦 ch2
被关闭和耗尽,<-ch2
就会返回 0
),如果 t1
较短,则 t2
将在末尾被隐式截断。也许在这个练习的目的上,你对这个假设是可以接受的,但如果你希望 Same
对于不同大小的树始终返回 false
,你可以将循环改为:
for i := range ch1 {
j, receivedJ := <-ch2
if i != j || !receivedJ {
return false
}
}
_, receivedJ := <-ch2
if receivedJ {
return false
}
(在这里我使用了 <-ch2
的两个返回值形式来检测通道是否已关闭和耗尽;如果 <-ch2
从通道中返回了一个实际值,receivedJ
将为 true
,如果它默认返回了 0
,则为 false
)。
英文:
The problem is that you never close ch1
, so your for i:= range ch1
loop never finishes; it just reads until there are no values in the channel, and then it blocks. At this point, there will be only one goroutine, and it's blocked listening to the empty channel, so Go will abort with the message you see. (Similarly, you never close ch2
, but in your case that doesn't happen to matter. That would cause a deadlock if ch2
ever had fewer values than ch1
.)
To be honest, I'm not sure exactly what solution the "Tour of Go" folks had in mind.
One option, which would work but is totally cheating, is to hard-code the fact that you'll only see ten values:
for i := 0; i < 10; i++ {
if <-ch1 != <-ch2 {
return false
}
}
A better option is for Same
to use anonymous functions that invoke Walk
and then close the channel:
go func() {
Walk(t1, ch1)
close(ch1)
}()
(or you could use a non-anonymous function for that; call it walkAndClose
or something).
Incidentally, your Same
function assumes that the two trees have the same sizes. If t1
has more elements, then t2
will be implicitly padded with zeroes at the end (since <-ch2
returns 0
once ch2
is closed and exhausted), and if t1
is shorter, then t2
will be implicitly truncated at the end. Maybe you're O.K. with making that assumption for the purposes of this exercise, but if you wanted Same
to always return false
for trees of different sizes, you could change your loop to:
for i := range ch1 {
j, receivedJ := <-ch2
if i != j || ! receivedJ {
return false
}
}
_, receivedJ := <-ch2
if receivedJ {
return false
}
(where I've used the two-return-value form of <-ch2
to detect whether the channel has been closed and exhausted; receivedJ
will be true
if <-ch2
returned an actual value from the channel, and false
if it returned a 0
by default).
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论