使用 select 的 Golang 频道不会停止

Go-lang 新手在这里。我正在尝试 Go 的 Tour of Go,并遇到了一个关于频道的练习 ( https://tour.golang.org/concurrency/7 )。这个想法是走两棵树,然后评估这些树是否相等。


我想通过选择等待两个渠道的结果来解决这个练习。当两者都完成时,我评估结果切片。不幸的是,该方法进行了无限循环。我添加了一些输出以查看发生了什么,并注意到只有一个通道被关闭,然后再次打开。


我显然做错了什么,但我看不出是什么。我的问题是我做错了什么?对于使下面的代码进入无限循环的通道关闭,我做了什么假设?


package main


import (

    "golang.org/x/tour/tree"

    "fmt"

)


// Walk walks the tree t sending all values

// from the tree to the channel ch.

func Walk(t *tree.Tree, ch chan int) {

    _walk(t, ch)

    close(ch)

}


func _walk(t *tree.Tree, ch chan int) {

    if (t.Left != nil) {

        _walk(t.Left, ch)

    }

    ch <- t.Value

    if (t.Right != nil) {

        _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)


    var out1 []int

    var out2 []int


    var tree1open, tree2open bool

    var tree1val, tree2val int

    for {

        select {

        case tree1val, tree1open = <- ch1:

            out1 = append(out1, tree1val)

        case tree2val, tree2open = <- ch2:

            out2 = append(out2, tree2val)

        default:

            if (!tree1open && !tree2open) {

                break

            } else {

                fmt.Println("Channel open?", tree1open, tree2open)

            }

        }

    }


    if (len(out1) != len(out2)) {

        return false

    }


    for i := 0 ; i < len(out1) ; i++ {

        if (out1[i] != out2[i]) {

            return false

        }

    }


    return true

}


func main() {

    ch := make(chan int)

    go Walk(tree.New(1), ch)


    for i := range ch {

        fmt.Println(i)

    }


    fmt.Println(Same(tree.New(1), tree.New(1)))

    fmt.Println(Same(tree.New(1), tree.New(2)))

}


互换的青春
浏览 334回答 2
2回答

眼眸繁星

“break”语句终止最里面的“for”、“switch”或“select”语句的执行。请参阅:http://golang.org/ref/spec#Break_statements 您示例中的 break 语句终止了 select 语句,即“最内层”语句。所以在for循环之前添加标签:ForLoop并添加break ForLoopForLoop:&nbsp; &nbsp; for {&nbsp; &nbsp; &nbsp; &nbsp; select {&nbsp; &nbsp; &nbsp; &nbsp; case tree1val, tree1open = <-ch1:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if tree1open {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; out1 = append(out1, tree1val)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else if !tree2open {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break ForLoop&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; case tree2val, tree2open = <-ch2:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if tree2open {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; out2 = append(out2, tree2val)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else if !tree1open {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break ForLoop&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }如果您想自己解决该问题,请不要阅读其余部分,并在完成后返回:解决方案 1(与您的类似):package mainimport "fmt"import "golang.org/x/tour/tree"// Walk walks the tree t sending all values// from the tree to the channel ch.func Walk(t *tree.Tree, ch chan int) {&nbsp; &nbsp; _walk(t, ch)&nbsp; &nbsp; close(ch)}func _walk(t *tree.Tree, ch chan int) {&nbsp; &nbsp; if t.Left != nil {&nbsp; &nbsp; &nbsp; &nbsp; _walk(t.Left, ch)&nbsp; &nbsp; }&nbsp; &nbsp; ch <- t.Value&nbsp; &nbsp; if t.Right != nil {&nbsp; &nbsp; &nbsp; &nbsp; _walk(t.Right, ch)&nbsp; &nbsp; }}// Same determines whether the trees// t1 and t2 contain the same values.func Same(t1, t2 *tree.Tree) bool {&nbsp; &nbsp; ch1, ch2 := make(chan int), make(chan int)&nbsp; &nbsp; go Walk(t1, ch1)&nbsp; &nbsp; go Walk(t2, ch2)&nbsp; &nbsp; tree1open, tree2open := false, false&nbsp; &nbsp; tree1val, tree2val := 0, 0&nbsp; &nbsp; out1, out2 := make([]int, 0, 10), make([]int, 0, 10)ForLoop:&nbsp; &nbsp; for {&nbsp; &nbsp; &nbsp; &nbsp; select {&nbsp; &nbsp; &nbsp; &nbsp; case tree1val, tree1open = <-ch1:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if tree1open {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; out1 = append(out1, tree1val)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else if !tree2open {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break ForLoop&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; case tree2val, tree2open = <-ch2:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if tree2open {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; out2 = append(out2, tree2val)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else if !tree1open {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break ForLoop&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }&nbsp; &nbsp; if len(out1) != len(out2) {&nbsp; &nbsp; &nbsp; &nbsp; return false&nbsp; &nbsp; }&nbsp; &nbsp; for i, v := range out1 {&nbsp; &nbsp; &nbsp; &nbsp; if v != out2[i] {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return false&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }&nbsp; &nbsp; return true}func main() {&nbsp; &nbsp; ch := make(chan int)&nbsp; &nbsp; go Walk(tree.New(1), ch)&nbsp; &nbsp; for i := range ch {&nbsp; &nbsp; &nbsp; &nbsp; fmt.Println(i)&nbsp; &nbsp; }&nbsp; &nbsp; fmt.Println(Same(tree.New(1), tree.New(1)))&nbsp; &nbsp; fmt.Println(Same(tree.New(1), tree.New(2)))}输出:12345678910truefalse&nbsp; &nbsp;&nbsp;其他方式:package mainimport "fmt"import "golang.org/x/tour/tree"// Walk walks the tree t sending all values// from the tree to the channel ch.func Walk(t *tree.Tree, ch chan int) {&nbsp; &nbsp; _walk(t, ch)&nbsp; &nbsp; close(ch)}func _walk(t *tree.Tree, ch chan int) {&nbsp; &nbsp; if t != nil {&nbsp; &nbsp; &nbsp; &nbsp; _walk(t.Left, ch)&nbsp; &nbsp; &nbsp; &nbsp; ch <- t.Value&nbsp; &nbsp; &nbsp; &nbsp; _walk(t.Right, ch)&nbsp; &nbsp; }}// Same determines whether the trees// t1 and t2 contain the same values.func Same(t1, t2 *tree.Tree) bool {&nbsp; &nbsp; ch1, ch2 := make(chan int), make(chan int)&nbsp; &nbsp; go Walk(t1, ch1)&nbsp; &nbsp; go Walk(t2, ch2)&nbsp; &nbsp; for v := range ch1 {&nbsp; &nbsp; &nbsp; &nbsp; if v != <-ch2 {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return false&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }&nbsp; &nbsp; return true}func main() {&nbsp; &nbsp; ch := make(chan int)&nbsp; &nbsp; go Walk(tree.New(1), ch)&nbsp; &nbsp; for v := range ch {&nbsp; &nbsp; &nbsp; &nbsp; fmt.Println(v)&nbsp; &nbsp; }&nbsp; &nbsp; fmt.Println(Same(tree.New(1), tree.New(1)))&nbsp; &nbsp; fmt.Println(Same(tree.New(1), tree.New(2)))}输出:12345678910truefalse&nbsp; &nbsp;&nbsp;

潇湘沐

Amd 的建议在上一个答案中是有效的。但是,看看您要解决的问题,它仍然没有解决它。(如果你运行程序,两种情况都会输出true)这是问题所在:for {&nbsp; &nbsp; select {&nbsp; &nbsp; case tree1val, tree1open = <-ch1:&nbsp; &nbsp; &nbsp; &nbsp; out1 = append(out1, tree1val)&nbsp; &nbsp; case tree2val, tree2open = <-ch2:&nbsp; &nbsp; &nbsp; &nbsp; out2 = append(out2, tree2val)&nbsp; &nbsp; default:&nbsp; &nbsp; &nbsp; &nbsp; //runtime.Gosched()&nbsp; &nbsp; &nbsp; &nbsp; if !tree1open && !tree2open {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break ForLoop&nbsp; &nbsp; &nbsp; &nbsp; } else {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fmt.Println("Channel open?", tree1open, tree2open)&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }}在这种情况下,由于 tree1open 和 tree2open 的默认值是 false (根据 golang 规范),它会进入“默认”情况,因为 select 是非阻塞的,并且只是从 ForLoop 中中断,甚至没有填充 out1 和out2 切片(可能,因为这些是 goroutines)。因此,out1 和 out2 的长度保持为零,因此在大多数情况下它输出 true。这是更正:ForLoop:for {&nbsp; &nbsp; select {&nbsp; &nbsp; case tree1val, tree1open = <-ch1:&nbsp; &nbsp; &nbsp; &nbsp; if tree1open {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; out1 = append(out1, tree1val)&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; if !tree1open && !tree2open {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break ForLoop&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; case tree2val, tree2open = <-ch2:&nbsp; &nbsp; &nbsp; &nbsp; if tree2open {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; out2 = append(out2, tree2val)&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; if !tree1open && !tree2open {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break ForLoop&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; default:&nbsp; &nbsp; }}需要注意的关键是,我们必须检查这两种情况下的通道是否已经关闭(相当于说tree1open和tree2open是否都是假的)。在这里,它将正确填充 out1 和 out2 切片,然后进一步比较它们各自的值。在 append 之前添加了对 tree1open(或 tree2open)是否为真的检查,只是为了避免将零值附加到 out1(或 out2)。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go