递归函数中关闭Go通道引发panic与死锁问题求助
嘿,这个问题我太熟了——Go里通道的关闭和递归结合的时候,很容易踩这个坑!咱们先把问题根源捋清楚,再给你解决办法~
你现在的问题出在两个地方:
- 通道只能被关闭一次,但你的递归函数每调用一次就执行
close(ch),递归到子节点时通道早就被根节点的调用关了,再关肯定panic; - 要是注释掉
close(ch),主goroutine用range遍历通道的时候,会一直等新数据,可遍历完所有节点后没人告诉它“没数据了”,直接就死锁了。
解决思路:把遍历和通道关闭拆分开
核心就是:只让整棵树遍历完成的那个时刻去关闭通道,而不是每个递归调用都碰关闭操作。最简洁的做法是在Walk函数里定义一个递归的辅助遍历函数,然后用一个单独的goroutine来执行遍历,等遍历彻底完成后再关闭通道。
修改后的完整代码如下:
package main import "golang.org/x/tour/tree" import "fmt" // Walk walks the tree t sending all values // from the tree to the channel ch. func Walk(t *tree.Tree, ch chan int) { // 定义递归遍历的辅助函数,只负责遍历发数据,不碰通道关闭 var traverse func(*tree.Tree) traverse = func(t *tree.Tree) { if t == nil { return } // 中序遍历:左子树 → 当前节点 → 右子树 traverse(t.Left) ch <- t.Value traverse(t.Right) } // 启动goroutine执行完整遍历,遍历结束后关闭通道 go func() { traverse(t) close(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) Walk(t1, ch1) Walk(t2, ch2) for { v1, ok1 := <-ch1 v2, ok2 := <-ch2 // 如果通道状态不一致,或者值不一样,说明两棵树不同 if ok1 != ok2 || v1 != v2 { return false } // 两个通道都关闭了,说明遍历完成且所有值都匹配 if !ok1 { return true } } } func main() { // 测试Walk函数 ch := make(chan int) Walk(tree.New(1), ch) for val := range ch { fmt.Println(val) } // 测试Same函数 fmt.Println("tree.New(1) == tree.New(1):", Same(tree.New(1), tree.New(1))) fmt.Println("tree.New(1) == tree.New(2):", Same(tree.New(1), tree.New(2))) }
为什么这样能解决问题?
- 避免重复关闭:辅助函数
traverse只做遍历和发数据,完全不处理通道关闭,递归调用时不会碰close(ch),自然不会出现重复关闭的panic; - 解决死锁:匿名goroutine会等
traverse执行完(也就是整棵树遍历完)才关闭通道,主goroutine的range ch会一直读到通道关闭,之后自动退出循环,不会一直阻塞等待。
要是你想用sync.WaitGroup的方式也可以,但对于树遍历这种场景,上面的写法更简洁,不需要额外引入同步包,代码可读性也更高。
内容的提问来源于stack exchange,提问作者Megidd
相关产品推荐
相关产品推荐

