递归函数必须带depth参数并校验,因go栈溢出panic无法recover捕获,需在入口用depth>maxdepth提前终止;maxdepth应按场景设定;defer+recover无效;推荐切片模拟栈迭代;-xss和setmaxstack对递归深度无用。

递归函数必须带 depth 参数并校验
Go 没有内置递归深度检查,runtime: goroutine stack exceeds 1GB limit 这类 panic 无法用 recover() 捕获——它发生在运行时栈保护层,早于 Go 的 panic 机制。所以不能靠异常兜底,必须在逻辑入口就掐断。
所有可能被用户输入触发的递归函数(如 JSON 解析、路径展开、模板 include),都要显式加 depth 参数:
- 初始调用传
0,每次递归传depth + 1 - 入口第一行写
if depth > maxDepth { return errors.New("recursion too deep") } -
maxDepth不能硬写成100,得按场景定:HTTP 头嵌套 ≤ 5 层,YAML include ≤ 8 次,这些要写进常量注释或配置里
别信 defer+recover 能防栈溢出
很多人在递归外层包一层 defer func() { if r := recover(); r != nil { ... } }(),结果线上照样崩。这不是写法错,是根本无效——栈溢出时 panic 尚未进入 Go 的 recover 流程,recover() 根本收不到信号。
实操上直接删掉这类代码,省得误导自己“已经加了防护”。真要容错,靠的是前面的 depth 校验,或者干脆不用递归。
用切片模拟栈替代递归更可靠
当输入深度不可控(比如解析用户上传的嵌套 JSON 或 AST),迭代比递归稳得多。Go 里用 []*Node 或 []state 模拟调用栈,配合 for 循环驱动:
- 把“递归参数”变成待处理任务,存进切片
- 每次
pop一个元素处理,子任务用append(stack, next)入栈 - 可随时加
if len(stack) > maxDepth实时限深,比函数参数更直观 - 树遍历、表达式求值、归并排序分治段都适用;归并排序里常见错误是
mid := len(slice) / 2,正确写法是mid := first + (last - first) / 2
-Xss 和 SetMaxStack 对 Go 递归深度毫无意义
Java 的 -Xss 是调线程栈大小,Go 没这参数;runtime/debug.SetMaxStack 控的是新 goroutine 的初始栈上限(默认 1GB),不是单次调用的递归深度阈值。调大它只会让崩溃来得更晚、更难定位;调小它反而会让合法的大局部变量分配失败。
真正该盯的是:你的递归是不是非用不可?能不能转成迭代?如果必须保留,depth 参数就是唯一靠谱的守门员——它不依赖运行时行为,不随 GOOS/GOARCH 变,也不受 JIT 或 GC 影响。
最易被忽略的点:终止条件必须可达、全覆盖、无遮挡。比如归并排序里用 len(slice) 判终止,实际只看原始切片长度,完全不管 first/last 区间,就会无限递归。这种 bug 不靠深度限制根本压不住,得先修逻辑。











