go-panic

1 概述

在go中,经常看到 panic 和 recover 与 defer配套使用。我们具体来看。

2 panic

panic的英文是恐慌。下面是panic方法的官方介绍,它会中止当前协程的正常执行。当一个方法F调用panic时,F的正常执行立即停止。任何被 F defer的方法,将会以正常的方式执行,然后F返回给它的调用方。

// 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.
func panic(v any)
import (
	"fmt"
)

func main() {
	A()
	return
}

func A() {
	fmt.Println("A before panic")
	panic("A panic")
	fmt.Println("A after panic")
	return
}

执行结果:

A before panic
panic: A panic

goroutine 1 [running]:
main.A()
        /Users/yiyunjie/go/src/Test/main.go:14 +0x68
main.main()
        /Users/yiyunjie/go/src/Test/main.go:8 +0x1c

Process finished with the exit code 2

我们可以看到,方法A正常地执行了第一个打印以及panic,之后协程中止运行, 后续的代码未按正常流程执行,协程以非正常方式退出,退出码为2。

很显然,在保持服务可用性的角度来看,panic并不是一种友好的退出方式,有没有一种更温和的处理方式呢?答案就是 defer 和 recover。

3 recover

我们在上一篇文章中讲过,defer会在函数return之前执行,但是panic很显然并不是return,那怎么办呢?这里就用到 recover。

recover 可以中止panic造成的协程非正常退出,从它的名称 恢复 就能看出一二,相当于给panic造了一个兜底的安全网。我们来看看。

// 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.
func recover() any

示例: 

package main

import (
	"fmt"
)

func main() {
	A()
	return
}

func A() {
	r := recover()
	fmt.Println(fmt.Sprintf("recover r:%+v", r))

	fmt.Println("A before panic")
	panic("A panic")
	fmt.Println("A after panic")
	return
}

输出

recover r:<nil>
A before panic
panic: A panic

goroutine 1 [running]:
main.A()
        /Users/yiyunjie/go/src/Test/main.go:17 +0xdc
main.main()
        /Users/yiyunjie/go/src/Test/main.go:8 +0x1c

Process finished with the exit code 2

很明显,如果直接把recover定义在函数体里面,它并不会中止panic,而是像正常代码一样顺序执行。所以我们必须要用到 defer,再试一次。 

package main

import (
	"fmt"
)

func main() {
	A()
	return
}

func A() {
	defer func() {
		r := recover()
		fmt.Println(fmt.Sprintf("recover r: %+v, type: %T", r, r))
	}()
	fmt.Println("A before panic")
	panic("A panic")
	fmt.Println("A after panic")
	return
}

输出

A before panic
recover r: A panic, type: string

Process finished with the exit code 0

这次协程正常地退出了,recover 输出的内容,正好就是 panic 输入的内容。

4 改造

我们获取到了panic的内容,想要对它做一些处理,再return给调用方,应该怎么做呢?

(待续)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章