意外的协程行为

我是 Golang 的初学者

我从这里阅读了 Go 中的并发性

在第 8 张幻灯片上出现问题之前,一切都很顺利
问题是:找出两个给定的二叉树是否等价。
我的方法:进行中序遍历,将两棵树的值保存在一个切片中并进行比较。

这是我的解决方案:[不完整]

package main


import (

    "fmt"

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

    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 func() {

        fmt.Println("executing first go routing")

        Walk(t1, ch1)

        fmt.Println("closing channel [ch1]")

        close(ch1)

    }()


    go func() {

        fmt.Println("executing second go routing")

        Walk( t2, ch2 )

        fmt.Println("closing channel [ch2]")

        close(ch2)

    }()


    shouldContinue := true

    var continue1, continue2 bool

    for shouldContinue {

        select {

        case r1, ok1 := <-ch1:

            fmt.Println("[ch1] [rcvd]", r1)

            continue1 = ok1


        case r2, ok2 := <-ch2:

            fmt.Println("[ch2] [rcvd]", r2)

            continue2 = ok2

        }

        shouldContinue = continue1 || continue2

    }

    return true

}


func main() {

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

}

我知道 goroutines 是合作调度的,如果它正在循环或连续计算,一个和完全阻塞另一个。所以我预计对于输出,它会首先从任一通道接收值,关闭它,然后它会从另一个通道接收值,然后关闭。一旦两者都关闭,for 循环就会中断。


令我惊讶的是,第一个 go 例程从未被安排好。这是我收到的输出:


executing second go routing

[ch2] [rcvd] 1

[ch2] [rcvd] 2

[ch2] [rcvd] 3

[ch2] [rcvd] 4

[ch2] [rcvd] 5

[ch2] [rcvd] 6

[ch2] [rcvd] 7

[ch2] [rcvd] 8

[ch2] [rcvd] 9

[ch2] [rcvd] 10

closing channel [ch2]

[ch2] [rcvd] 0 

谁能解释这里发生了什么?一旦 channel2 关闭并且第二个例程完成,为什么第一个不执行?


任何帮助,将不胜感激。谢谢。


白板的微信
浏览 133回答 2
2回答

蛊毒传说

关闭 channel2 后,为什么第一个不执行?通道不执行。一遍又一遍地执行的是您的选择。请注意,无论通道是否关闭,这两种情况都可以始终执行。所以 select 可以选择 id 所做的第二种情况,而你中止了。(你的中止条件看起来可疑:一旦两个通道都关闭,即如果ok1和 ok2 都为假,你就完成了)。不要将 select 本身视为“goroutine 调度工具”。它不是。它将随机选择一个可运行的案例。如果您所有的案例都是可val, ok := <- ch运行的,那么 select 可能总是选择第二个。或第一个,或...[第二个解决方案]我现在更加困惑是怎么回事了。您的中止条件不同。一旦两个通道都为零,您就会中断,这会在两个通道都关闭后发生。这与您的第一个解决方案不同,因为一旦任何&nbsp;一个通道关闭,第一个就会中断。这里的并发性问题不是 goroutine 调度,而只是你的 for 循环执行选择的中止条件。它们不同于第一个和第二个,第一个从根本上是错误的,因为一旦任何通道耗尽它就会停止。

噜噜哒

在你的代码的第一部分,你的逻辑有错误。shouldContinue := truevar continue1, continue2 boolfor shouldContinue {    select {    case r1, ok1 := <-ch1:        fmt.Println("[ch1] [rcvd]", r1)        continue1 = ok1    case r2, ok2 := <-ch2:        fmt.Println("[ch2] [rcvd]", r2)        continue2 = ok2    }    shouldContinue = continue1 || continue2}在上面的代码中,continue1和continue2是false. select在他的一个案例完成之前一直处于阻塞状态。让我们先说case r2, ok2 := <-ch2:满足,然后continue2才是true。由于shouldContinue = continue1 || continue2这种情况,for循环将继续。出于某种原因(去例行调度)case r2, ok2 := <-ch2:条件每次都满足。现在当关闭时ch2,价值ok2也会false如此。现在, 和都是,也将是。因此它打破了循环,你看不到输出。试试这个:continue2falsecontinue1continue2falseshouldContinuefalseforch1continue1 = truecontinue2 = truefor shouldContinue {    select {    case r1, ok1 := <-ch1:        fmt.Println("[ch1] [rcvd]", r1)        continue1 = ok1    case r2, ok2 := <-ch2:        fmt.Println("[ch2] [rcvd]", r2)        continue2 = ok2    }    shouldContinue = continue1 || continue2}当一个通道关闭时,你不能在这个通道上发送值,但你仍然可以从通道接收。Nil 通道总是阻塞,并且您还更改了for循环中断逻辑。这就是您的第二个解决方案有效的原因。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go