duan246558 2019-03-26 11:03
浏览 64
已采纳

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

(https://github.com/golang/tour/blob/master/solutions/binarytrees_quit.go) for Exercise: Equivalent Binary Trees Supposed we have two simple equivalent binary trees "1 3 5" and "2 3 5". When two goroutines "Walk" walks at leaf "1" and "2" concurrently,

if v1 != v2 {
    return false
}

this condition in function Same will be true and

close(quit)

will run.

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)
}

Channel "quit" will receive message and the second case of select statement will execute. And then it will go back to the upper level function "walkImpl" and keep on running the last line walkImpl(t.Right, ch, quit). So is there goroutine leak under the circumstance, cause channel "quit" is already read out, which can't be read again in the upper level? Function "Walk" also can't go back to "close" handler.

  • 写回答

1条回答 默认 最新

  • duancao1951 2019-03-26 12:03
    关注

    When multiple goroutines are targeted with a cancellation signal, most often it is done by closing a channel, and not sending a value on the channel. Receiving from a closed channel can proceed immediately, no matter how many goroutines do that. A value sent on a channel can be received at most once, so it is not suitable to signal multiple goroutines with a value. Spec: Receive operator:

    A receive operation on a closed channel can always proceed immediately, yielding the element type's zero value after any previously sent values have been received.

    Now, if you close the quit channel, that does not guarantee your function will return immediately.

    First, you recurse down to the left child without checking quit, which call will do the same (until a nil left child is reached).

    Second, if a value can be sent on ch, then both cases are ready and thus select chooses one of them randomly, which may or may not be the quit case. For details, see How does select work when multiple channels are involved?

    If you want to avoid these, you should add a non-blocking quit check as the first thing in your function:

    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)
    }
    

    Now one could ask if we still need the quit case in the second select since we've already checked it first thing in walkImpl(). The answer is that you should keep that too, because if sending on ch would block (e.g. the consumer would be shut down when quit is closed), that send operation could block forever. This way (when quit is closed) it is guaranteed that the function returns.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 素材场景中光线烘焙后灯光失效
  • ¥15 请教一下各位,为什么我这个没有实现模拟点击
  • ¥15 执行 virtuoso 命令后,界面没有,cadence 启动不起来
  • ¥50 comfyui下连接animatediff节点生成视频质量非常差的原因
  • ¥20 有关区间dp的问题求解
  • ¥15 多电路系统共用电源的串扰问题
  • ¥15 slam rangenet++配置
  • ¥15 有没有研究水声通信方面的帮我改俩matlab代码
  • ¥15 ubuntu子系统密码忘记
  • ¥15 保护模式-系统加载-段寄存器