在GoLang教程中出现了死锁错误。

huangapple go评论85阅读模式
英文:

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 &lt;- 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 != &lt;-ch2 {
		
			return false
		}
	}
	
	
	return true

}

func main() {
	isSame := Same(tree.New(1), tree.New(1))	
	if isSame {
		fmt.Println(&quot;SAME&quot;)
	} else {
		fmt.Println(&quot;DIFF&quot;)
	}
}

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 的两个返回值形式来检测通道是否已关闭和耗尽;如果 &lt;-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 &lt; 10; i++ {
    if &lt;-ch1 != &lt;-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 &lt;-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 := &lt;-ch2
    if i != j || ! receivedJ {
        return false
    }
}
_, receivedJ := &lt;-ch2
if receivedJ {
    return false
}

(where I've used the two-return-value form of &lt;-ch2 to detect whether the channel has been closed and exhausted; receivedJ will be true if &lt;-ch2 returned an actual value from the channel, and false if it returned a 0 by default).

huangapple
  • 本文由 发表于 2014年12月15日 08:33:02
  • 转载请务必保留本文链接:https://go.coder-hub.com/27475791.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定