为了账号安全,请及时绑定邮箱和手机立即绑定

使用 select 的 Golang 频道不会停止

使用 select 的 Golang 频道不会停止

Go
互换的青春 2022-01-17 18:37:58
Go-lang 新手在这里。我正在尝试 Go 的 Tour of Go,并遇到了一个关于频道的练习 ( https://tour.golang.org/concurrency/7 )。这个想法是走两棵树,然后评估这些树是否相等。我想通过选择等待两个渠道的结果来解决这个练习。当两者都完成时,我评估结果切片。不幸的是,该方法进行了无限循环。我添加了一些输出以查看发生了什么,并注意到只有一个通道被关闭,然后再次打开。我显然做错了什么,但我看不出是什么。我的问题是我做错了什么?对于使下面的代码进入无限循环的通道关闭,我做了什么假设?package mainimport (    "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)))}
查看完整描述

2 回答

?
眼眸繁星

TA贡献1873条经验 获得超9个赞

“break”语句终止最里面的“for”、“switch”或“select”语句的执行。

请参阅:http

://golang.org/ref/spec#Break_statements 您示例中的 break 语句终止了 select 语句,即“最内层”语句。

所以在for循环之前添加标签:ForLoop并添加break ForLoop


ForLoop:

    for {

        select {

        case tree1val, tree1open = <-ch1:

            if tree1open {

                out1 = append(out1, tree1val)

            } else if !tree2open {

                break ForLoop

            }

        case tree2val, tree2open = <-ch2:

            if tree2open {

                out2 = append(out2, tree2val)

            } else if !tree1open {

                break ForLoop

            }

        }

    }

如果您想自己解决该问题,请不要阅读其余部分,并在完成后返回:解决方案 1(与您的类似):


package main


import "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) {

    _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, ch2 := make(chan int), make(chan int)

    go Walk(t1, ch1)

    go Walk(t2, ch2)


    tree1open, tree2open := false, false

    tree1val, tree2val := 0, 0

    out1, out2 := make([]int, 0, 10), make([]int, 0, 10)

ForLoop:

    for {

        select {

        case tree1val, tree1open = <-ch1:

            if tree1open {

                out1 = append(out1, tree1val)

            } else if !tree2open {

                break ForLoop

            }

        case tree2val, tree2open = <-ch2:

            if tree2open {

                out2 = append(out2, tree2val)

            } else if !tree1open {

                break ForLoop

            }

        }

    }

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

        return false

    }

    for i, v := range out1 {

        if v != 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)))

}

输出:


1

2

3

4

5

6

7

8

9

10

true

false    

其他方式:


package main


import "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) {

    _walk(t, ch)

    close(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, ch2 := make(chan int), make(chan int)

    go Walk(t1, ch1)

    go Walk(t2, ch2)

    for v := range ch1 {

        if v != <-ch2 {

            return false

        }

    }

    return true

}


func main() {

    ch := make(chan int)

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

    for v := range ch {

        fmt.Println(v)

    }

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

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

}

输出:


1

2

3

4

5

6

7

8

9

10

true

false    


查看完整回答
反对 回复 2022-01-17
?
潇湘沐

TA贡献1816条经验 获得超6个赞

Amd 的建议在上一个答案中是有效的。但是,看看您要解决的问题,它仍然没有解决它。(如果你运行程序,两种情况都会输出true)


这是问题所在:


for {

    select {

    case tree1val, tree1open = <-ch1:

        out1 = append(out1, tree1val)

    case tree2val, tree2open = <-ch2:

        out2 = append(out2, tree2val)

    default:

        //runtime.Gosched()

        if !tree1open && !tree2open {

            break ForLoop

        } else {

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

        }

    }

}

在这种情况下,由于 tree1open 和 tree2open 的默认值是 false (根据 golang 规范),它会进入“默认”情况,因为 select 是非阻塞的,并且只是从 ForLoop 中中断,甚至没有填充 out1 和out2 切片(可能,因为这些是 goroutines)。因此,out1 和 out2 的长度保持为零,因此在大多数情况下它输出 true。


这是更正:


ForLoop:

for {

    select {

    case tree1val, tree1open = <-ch1:

        if tree1open {

            out1 = append(out1, tree1val)

        }

        if !tree1open && !tree2open {

            break ForLoop

        }

    case tree2val, tree2open = <-ch2:

        if tree2open {

            out2 = append(out2, tree2val)

        }

        if !tree1open && !tree2open {

            break ForLoop

        }

    default:


    }

}

需要注意的关键是,我们必须检查这两种情况下的通道是否已经关闭(相当于说tree1open和tree2open是否都是假的)。在这里,它将正确填充 out1 和 out2 切片,然后进一步比较它们各自的值。


在 append 之前添加了对 tree1open(或 tree2open)是否为真的检查,只是为了避免将零值附加到 out1(或 out2)。


查看完整回答
反对 回复 2022-01-17
  • 2 回答
  • 0 关注
  • 333 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号