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給調用方,應該怎麼做呢?

(待續)

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