({https://github.com/golang/tour/blob/master/solutions/binarytrees_quit.go)练习:等效二叉树 假设我们有两个简单的等效二叉树“ 1 3 5”和“ 2 3 5”。当两个goroutines“ Walk”同时在叶“ 1”和“ 2”处走动时,
if v1 != v2 {
return false
}
Same函数中的此条件为真,
close(quit)
将运行。
func walkImpl(t *tree.Tree, ch, quit chan int) {
if t == nil {
return
}
walkImpl(t.Left, ch, quit)
select {
case ch <- t.Value:
// Value successfully sent.
case <-quit:
return
}
walkImpl(t.Right, ch, quit)
}
通道“ quit”将收到消息,并且select语句的第二种情况将执行。然后它将返回上级功能“ walkImpl”,并继续运行最后一行walkImpl(t.Right, ch, quit)
。那么在这种情况下是否存在goroutine泄漏,导致通道“ quit”已经被读出,而在上层不能再次读取?函数“步行”也不能返回到“关闭”处理程序。
答案 0 :(得分:2)
当多个goroutine以取消信号作为目标时,通常是通过关闭通道,而不在通道上发送值来完成的。无论有多少goroutine这样做,从封闭通道接收都可以立即进行。在一个通道上发送的值最多只能接收一次,因此不适合用一个值来发信号通知多个goroutine。 Spec: Receive operator:
在closed通道上的接收操作总是可以立即进行,在收到任何先前发送的值之后会产生元素类型的zero value。
现在,如果您关闭quit
通道,则不能保证您的函数将立即返回。
首先,您无需检查quit
就向下递归到左孩子,该调用将执行相同操作(直到到达nil
左孩子为止)。
第二,如果可以在ch
上发送值,则两种情况都准备就绪,因此select
随机选择其中一种,这可以是quit
,也可以不是。有关详细信息,请参见How does select work when multiple channels are involved?
如果要避免这些情况,则应在功能中首先添加无阻塞的quit
检查:
func walkImpl(t *tree.Tree, ch, quit chan int) {
select {
case <-quit:
return
default: // This empty default makes it a non-blocking check
}
if t == nil {
return
}
walkImpl(t.Left, ch, quit)
select {
case ch <- t.Value:
// Value successfully sent.
case <-quit:
return
}
walkImpl(t.Right, ch, quit)
}
现在,有人可能会问我们在第二个quit
中是否还需要select
的情况,因为我们已经在walkImpl()
中检查了第一件事。答案是您也应该保留该设置,因为如果在ch
上发送会阻塞(例如,在quit
关闭时,使用者将被关闭),则该发送操作可能会永远阻塞。这样(quit
关闭时)可以确保函数返回。