我是Golang的初学者
我从here.那里读到了Go中的并发性
一切正常,直到在8th slide.上向我提出问题为止
问题是:找出两个给定的二叉树是否相等。
我的方法:进行有序遍历,将两棵树中的值保存在一个切片中并进行比较。
这是我的解决方法:[不完整]
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))
}
我知道goroutine是协同调度的,并且如果它正在循环或连续进行计算,则一个goroutine会完全阻塞另一个。因此,我希望对于输出,它将首先从任何一个通道接收值,将其关闭,然后从另一个通道接收值,然后关闭。两者都关闭后,for循环将中断。
令我惊讶的是,先行程序从未安排好。这是我收到的输出:
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关闭并且第二个go例程结束,为什么第一个不执行?
任何帮助将不胜感激。谢谢。
更新:
我搜寻了有关突破频道的信息,然后发现了一个问题here.
据此,我对解决方案进行了如下更新:
package main
import (
"fmt"
"golang.org/x/tour/tree"
// "time"
)
// Walk walks the tree t sending all values
// from the tree to the channel ch.
func Walk(t *tree.Tree, ch chan int) {
// time.Sleep(time.Millisecond)
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)
}()
for {
select {
case r1, ok1 := <-ch1:
fmt.Println("[ch1] [rcvd]", r1)
if !ok1 {
ch1 = nil
}
case r2, ok2 := <-ch2:
fmt.Println("[ch2] [rcvd]", r2)
if !ok2 {
ch2 = nil
}
}
if ch1 == nil && ch2 == nil {
break
}
}
return true
}
func main() {
Same(tree.New(1), tree.New(1))
}
哪个给出了我认为第一个代码段会输出的确切输出:
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
executing first go routing
[ch1] [rcvd] 1
[ch1] [rcvd] 2
[ch1] [rcvd] 3
[ch1] [rcvd] 4
[ch1] [rcvd] 5
[ch1] [rcvd] 6
[ch1] [rcvd] 7
[ch1] [rcvd] 8
[ch1] [rcvd] 9
[ch1] [rcvd] 10
closing channel [ch1]
[ch1] [rcvd] 0
我现在对发生的事情更加困惑。
答案 0 :(得分:3)
关闭channel2后,为什么第一个不执行?
不执行通道。一遍又一遍执行的是您的选择。请注意,无论通道是否关闭,两种情况都可以始终执行。因此,可以选择id所做的第二种情况并且您中止了。 (您的中止条件看起来很糟糕:关闭两个通道(即如果两个 ok1和ok2均为假),就可以完成操作。)
不要将select本身视为“ goroutine调度工具”。它不是。它将随机选择可运行案例之一。如果所有案例的格式均为val, ok := <- ch
,则所有案例都是可运行的,并且select可能始终选择第二个案例。或第一个,或...
[第二个解决方案]我现在对发生的事情更加困惑。
中止条件不同。当两个通道都为零时,您将中断,这将在两个通道都关闭后发生。这与您的第一个解决方案有所不同,因为第一个解决方案会在任何 一个通道关闭后中断。
这里并发性的问题不是goroutine调度,而仅仅是您进行选择的for循环的中止条件。它们不同于第一个和第二个,第一个从根本上是错误的,因为一旦任何通道用尽,它都会停止。
答案 1 :(得分:2)
在代码的第一部分,您的逻辑中有错误。
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
,因此continue2
也将为false
。现在,continue1
和continue2
都是false
,因此shouldContinue
也将是false
。因此,它中断了for
循环,您看不到ch1
的输出。尝试以下方法:
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
}
关闭通道后,无法在该通道上发送值,但仍可以从该通道接收。参见此处:https://play.golang.org/p/S4USguWfN_z。
无频道始终处于阻塞状态,并且您还更改了for
循环中断逻辑。这就是您的第二个解决方案起作用的原因。