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

意外的协程行为

意外的协程行为

Go
白板的微信 2023-06-01 17:02:39
我是 Golang 的初学者我从这里阅读了 Go 中的并发性。在第 8 张幻灯片上出现问题之前,一切都很顺利。问题是:找出两个给定的二叉树是否等价。我的方法:进行中序遍历,将两棵树的值保存在一个切片中并进行比较。这是我的解决方案:[不完整]package mainimport (    "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] 10closing channel [ch2][ch2] [rcvd] 0 谁能解释这里发生了什么?一旦 channel2 关闭并且第二个例程完成,为什么第一个不执行?任何帮助,将不胜感激。谢谢。
查看完整描述

2 回答

?
蛊毒传说

TA贡献1895条经验 获得超3个赞

关闭 channel2 后,为什么第一个不执行?

通道不执行。一遍又一遍地执行的是您的选择。请注意,无论通道是否关闭,这两种情况都可以始终执行。所以 select 可以选择 id 所做的第二种情况,而你中止了。(你的中止条件看起来可疑:一旦两个通道都关闭,即如果ok1和 ok2 都为假,你就完成了)。

不要将 select 本身视为“goroutine 调度工具”。它不是。它将随机选择一个可运行的案例。如果您所有的案例都是可val, ok := <- ch运行的,那么 select 可能总是选择第二个。或第一个,或...

[第二个解决方案]我现在更加困惑是怎么回事了。

您的中止条件不同。一旦两个通道都为零,您就会中断,这会在两个通道都关闭后发生。这与您的第一个解决方案不同,因为一旦任何 一个通道关闭,第一个就会中断。

这里的并发性问题不是 goroutine 调度,而只是你的 for 循环执行选择的中止条件。它们不同于第一个和第二个,第一个从根本上是错误的,因为一旦任何通道耗尽它就会停止。


查看完整回答
反对 回复 2023-06-01
?
噜噜哒

TA贡献1784条经验 获得超7个赞

在你的代码的第一部分,你的逻辑有错误。


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

}

在上面的代码中,continue1和continue2是false. select在他的一个案例完成之前一直处于阻塞状态。让我们先说case r2, ok2 := <-ch2:满足,然后continue2才是true。由于shouldContinue = continue1 || continue2这种情况,for循环将继续。出于某种原因(去例行调度)case r2, ok2 := <-ch2:条件每次都满足。现在当关闭时ch2,价值ok2也会false如此。现在, 和都是,也将是。因此它打破了循环,你看不到输出。试试这个:continue2falsecontinue1continue2falseshouldContinuefalseforch1


continue1 = true

continue2 = true

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

}

当一个通道关闭时,你不能在这个通道上发送值,但你仍然可以从通道接收。


Nil 通道总是阻塞,并且您还更改了for循环中断逻辑。这就是您的第二个解决方案有效的原因。


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

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信