练习:等效二叉树解决方案中的内存泄漏?

时间:2019-03-26 11:03:44

标签: go memory-leaks

({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”已经被读出,而在上层不能再次读取?函数“步行”也不能返回到“关闭”处理程序。

1 个答案:

答案 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关闭时)可以确保函数返回。