Go捕获错误panic、打印错误栈、触发panic的情况

recover是用来捕获panic信息,重新获取协程控制的函数。
只能捕获用户态下的panic,如主动触发panic函数、被动调用的空指针取值、索引越界、栈溢出、改写只读内存等。
对fatalthrow、fatalpanic等panic无法捕获,因为他们直接调用了exit()退出了。

捕获错误panic、打印错误栈

func TestPanic(t *testing.T) {
    
    
	j := 0
	for i := 0; i <= 10; i++ {
    
    
		func() {
    
     // try except
			defer func() {
    
    
				if err := recover(); err != nil {
    
    
					fmt.Println("panic recover!")
					// 打印错误和堆栈
					fmt.Printf("err=%v, stack=%s\n", err, string(debug.Stack()))
				}
			}()
			fmt.Printf("%v\n", i)
			x := i / j // 除0报错
			fmt.Printf("%v", x)
		}()
	}
}

以上代码,通过匿名函数把defer和报错代码放到一个协程里,让recover能捕获到错误。
panic只会触发当前goroutine的defer,recover只有在defer中调用才能生效,panic允许在defer中嵌套多次调用。

defer跨协程失效

func TestPanic1(t *testing.T) {
    
    
	// 跨协程失效,下面一行的defer不会执行
	defer println("in main")

	go func() {
    
    
		defer println("in goroutine")
		fmt.Println("子协程running")
		panic("子协程崩溃")
	}()

	time.Sleep(1 * time.Second)
}

子协程里defer的和主协程里的defer不在一个协程里,所以子协程panic后,主线程中的defer并不会执行。

recover只在发生panic之后调用才会生效

func TestPanic2(t *testing.T) {
    
    
	defer fmt.Println("in main")
	if err := recover(); err != nil {
    
    
		fmt.Println("occur error")
		fmt.Println(err)
	}

	panic("unknown err")
}

以上代码中,发送错误之前,已经调用了recover,导致recover无法捕获错误,正确写法是放到defer里,延迟调用。

func TestPanic3(t *testing.T) {
    
    
	defer fmt.Println("in main")
	defer func() {
    
    
		if err := recover(); err != nil {
    
    
			fmt.Println("occur error")
			fmt.Println(err)
		}
	}()

	panic("unknown err")
}

recover的作用是捕获panic,从而恢复正常代码执行。
recover必须配合defer使用。
recover没有传入参数,但是有返回值,返回值就是panic传递的值。

panic嵌套调用顺序

func TestPanic4(t *testing.T) {
    
    
	defer fmt.Println("in main")
	defer func() {
    
    
		defer func() {
    
    
			panic("panic again and again")
		}()
		panic("panic again")
	}()
	// 主动触发panic
	panic("panic once")
}

// in main
// --- FAIL: TestPanic4 (0.00s)
// panic: panic once
//         panic: panic again
//         panic: panic again and again [recovered]
//         panic: panic again and again

如果函数F中书写并触发了panic语句,会终止其后要执行的代码。在panic所在函数F内如果存在要执行的defer函数列表,则按照defer书写顺序的逆序执行;
如果函数G调用函数F,则函数F panic后返回调用者函数G。函数G中,调用函数F语句之后的语句都不会执行。假如函数G中也有要执行的defer函数列表,则按照defer书写顺序的逆序子还行;
退出整个goroutine,并报告错误。

recover只会捕获最后一个panic

func TestPanic5(t *testing.T) {
    
    
	defer func() {
    
    
		if err := recover(); err != nil {
    
    
			fmt.Println(err)
		}
	}()
	defer func() {
    
    
		panic("three")
	}()
	defer func() {
    
    
		panic("two")
	}()
	panic("one")
	// three
}

常见的触发panic情况

数组下标越界(运行时错误)

func TestPanic6(t *testing.T) {
    
    
	var s []string
	fmt.Println(s)
	fmt.Println(s[0])
	// panic: runtime error: index out of range [0] with length 0 [recovered]
	//     panic: runtime error: index out of range [0] with length 0
}

空指针异常(运行时错误)

func TestPanic7(t *testing.T) {
    
    
	type Person struct {
    
    
		Name string
		Age  int
	}
	var p *Person
	fmt.Println(p)
	fmt.Println(p.Name)
	//		panic: runtime error: invalid memory address or nil pointer dereference [recovered]
	//	        panic: runtime error: invalid memory address or nil pointer dereference
	//
	// [signal 0xc0000005 code=0x0 addr=0x0 pc=0xafe05b]
}

类型断言失败(接口转换异常)

func add(a, b interface{
    
    }) {
    
    
	i := a.(int)
	j := b.(int)
	fmt.Println(i + j)
}
func TestPanic8(t *testing.T) {
    
    
	add(20, 18)
	add(1, "hello")
	//	38
	//
	// --- FAIL: TestPanic8 (0.00s)
	// panic: interface conversion: interface {} is string, not int [recovered]
	//
	//	panic: interface conversion: interface {} is string, not int
}

通道为空,通道已关闭(写数据)

func TestPanic9(t *testing.T) {
    
    
	var ch chan int
	close(ch)
	// panic: close of nil channel [recovered]
    //     panic: close of nil channel
}

猜你喜欢

转载自blog.csdn.net/lilongsy/article/details/131381452