go中如何使用recover

go 中如何使用 recover 恢复 panic。

常驻服务(比如 web server),程序 panic 之后,为了能够保持继续提供服务的能力,需要使用 recover 函数处理 panic 错误。使用 recover 函数时,有以下几点需要注意:

  1. 只在当前 goroutine 有效

go 源码中 panic 函数文档注释如下:

// The panic built-in function stops normal execution of the current
// goroutine. When a function F calls panic, normal execution of F stops
// immediately. Any functions whose execution was deferred by F are run in
// the usual way, and then F returns to its caller. To the caller G, the
// invocation of F then behaves like a call to panic, terminating G's
// execution and running any deferred functions. This continues until all
// functions in the executing goroutine have stopped, in reverse order. At
// that point, the program is terminated with a non-zero exit code. This
// termination sequence is called panicking and can be controlled by the
// built-in function recover.

当函数 F 发生 panic 时,在 F 的 defer 函数执行后,会将控制权转交给 F 调用者 G。对 G 而言,函数 F 的行为会跟调用 panic 类似,终止 G 的正常执行流程。以此类推,整个程序最终会以非零退出码停止。对 G 而言,F 效果类似于调用 panic 函数,但并不完全等效,G 中无法用 recover 函数去管理 F 的 panic 退出。测试代码如下:

func panicTest() {
	defer func() {
		if err := recover(); err != nil {
			log.Printf("recover error:%+v", err)

			panic("for panic")
		}
	}()

	panic("panic mock")
}

func main() {
	defer func() {
		if err := recover(); err != nil {
			log.Printf("main recover: %+v", err)
		}
	}()
	go panicTest()
loop:
	goto loop
}

程序测试结果:

❯ go run howtorecover.go
2022/11/13 16:56:08 recover error:panic mock
panic: panic mock [recovered]
        panic: for panic

goroutine 18 [running]:
main.panicTest.func1()
        /Users/brick/Desktop/howtorecover.go:13 +0x70
panic({0x1098440, 0x10c9b48})
        /usr/local/Cellar/go/1.19.3/libexec/src/runtime/panic.go:884 +0x212
main.panicTest()
        /Users/brick/Desktop/howtorecover.go:17 +0x49
created by main.main
        /Users/brick/Desktop/howtorecover.go:47 +0x45
exit status 2
  1. 只在 defer 中有效

go 源码中 recover 注释如下:

// The recover built-in function allows a program to manage behavior of a
// panicking goroutine. Executing a call to recover inside a deferred
// function (but not any function called by it) stops the panicking sequence
// by restoring normal execution and retrieves the error value passed to the
// call of panic. If recover is called outside the deferred function it will
// not stop a panicking sequence. In this case, or when the goroutine is not
// panicking, or if the argument supplied to panic was nil, recover returns
// nil. Thus the return value from recover reports whether the goroutine is
// panicking.

goroutine 在 panic 后,控制权会转交当前函数的 defer 函数。所以 recover 如果不作用于 defer 中,无法获取 panic 后函数的控制权。在 go 源码注释中已有明确说明。

  1. recover 可嵌套调用

在 recover 中可继续调用 panic 和 recover。验证代码如下:

func recover2() {
	defer func() {
		if err := recover(); err != nil {
			log.Printf("recover2 error:%+v", err)
		}
	}()

	panic("recover2")
}

func recover1() {
	defer func() {
		if err := recover(); err != nil {
			log.Printf("recover1 error:%+v", err)
			recover2()
		}
	}()

	panic("recover1")
}

func main() {
	defer func() {
		if err := recover(); err != nil {
			log.Printf("main recover: %+v", err)
		}
	}()
	go recover1()
loop:
	goto loop
}

代码输出如下:

❯ go run howtorecover.go
2022/11/13 17:06:52 recover1 error:recover1
2022/11/13 17:06:52 recover2 error:recover2

参考链接

panic 和 recover