首页 > 编程笔记 > Go语言笔记 阅读:17

Go语言panic()和recover()的用法(非常详细)

在 Go 语言中,程序在编译时可能捕获到一些错误。有些错误只能在运行时出现,例如数组访问越界、空指针引用等,这些运行时出现的错误都会引起宕机(panic)。

当宕机发生时,程序停止运行,编译器输出对应的报错信息。如果在宕机后想让程序继续执行,则可以使用宕机恢复(recover)机制。

Go语言panic()宕机

panic() 是 Go语言的内置函数。它类似于其他编程语言中抛出异常的 throw 语句。panic() 一般用在函数内部。

panic() 函数的语法格式如下:
func panic(v interface{})
panic() 函数的参数可以是任意类型的值。

1) 手动触发宕机

在程序中可以手动触发宕机使程序崩溃,这样可以使开发者及时发现错误,减少可能的损失。

在手动触发宕机时,堆栈和 goroutine 信息将输出到控制台,所以通过宕机也可以方便地查找发生错误的位置,有利于及时排查和解决问题。

代码如下:
package main

func main() {
    panic("Program crash")
}
运行结果如下:

panic: Program crash

goroutine 1 [running]:
main.main()
    e:/Code/10/demo.go:4 +0x27
exit status 2

2) 宕机时触发defer语句

当调用函数执行到 panic() 时,不执行 panic() 后面的代码,如果在 panic() 函数前面有 defer 语句则正常执行该语句,之后返回调用函数,执行每一层的 defer 语句,直到所有正在执行的函数都被终止为止。

代码如下:
package main

import "fmt"

func test() {
    defer func() {
        fmt.Println("exit func test")
    }()
    panic("Program crash") // 触发宕机
}

func main() {
    defer func() {
        fmt.Println("exit func main")
    }()
    test() // 运行代码不会执行
    fmt.Println("不会执行")
}
运行结果如下:

exit func test
exit func main
panic: Program crash

goroutine 1 [running]:
main.main()
    e:/Code/10/demo.go:9 +0x49
main.test()
    e:/Code/10/demo.go:15 +0x3f
exit status 2

根据运行结果分析代码的执行流程如下:
在触发 panic 后执行的 defer 语句内还可以继续触发 panic,进一步抛出异常,直到程序整体崩溃。

Go语言recover()宕机恢复

Go语言中,使用 recover() 可以在宕机后让程序继续执行。recover() 是 Go语言的内置函数,该函数可以捕获 panic 信息,类似于其他编程语言中用于捕获异常的 try…catch 语句。recover 通常在使用 defer 语句的函数中执行。

如果当前执行的函数发生 panic,那么调用 recover() 函数可以获取 panic 信息,并且恢复程序正常执行。

示例代码如下:
package main

import "fmt"

func test() {
    defer func() {
        err := recover()
        if err != nil {
            fmt.Println(err)
        }
        fmt.Println("恢复执行")
    }()
    panic("程序崩溃") // 触发宕机
}

func main() {
    fmt.Println("程序开始")
    test()
    fmt.Println("程序结束")
}
运行结果如下:

程序开始
程序崩溃
恢复执行
程序结束

由运行结果可以看出,通过 recover() 函数可以获取 panic 信息,后面的程序可以正常按顺序执行。

如果 panic() 函数和 recover() 函数一起使用,并且程序中的函数调用比较复杂,则在执行完对应的 defer 语句后,程序退出当前函数并返回到调用处继续执行。代码如下:
package main

import "fmt"

func first() {
    fmt.Println("first 函数开始")
    second()
    fmt.Println("first 函数结束")
}

func second() {
    defer func() { recover() }()
    fmt.Println("second 函数开始")
    third()
    fmt.Println("second 函数结束")
}

func third() {
    fmt.Println("third 函数开始")
    panic("Program crash")  // 触发宕机
    fmt.Println("third 函数结束")
}

func main() {
    fmt.Println("程序开始")
    first()
    fmt.Println(19)
    fmt.Println("程序结束")
}
运行结果为:

程序开始
first 函数开始
second 19开始
Program crash
second 函数结束
first 函数结束
程序结束

由运行结果可以看出,在 third() 函数中触发宕机,程序执行 second() 函数中的 defer 语句,在执行完该语句后退出 second() 函数,并返回调用该函数的 first() 函数继续执行。

注意,虽然 panic() 函数和 recover() 函数可以模拟其他语言的异常机制,但在编写普通函数时不建议使用这种特性。

如果 defer 语句中也存在 panic,那么只有最后一个 panic 可以被 recover() 函数捕获。代码如下:
package main

import "fmt"

func main() {
    defer func() {
        err := recover()
        if err != nil {
            fmt.Println(err)
        }
        fmt.Println("恢复执行")
    }()
    defer func() {
        panic("defer panic") // 触发宕机
    }()
    panic("panic") // 触发宕机
}
运行结果如下:

defer panic
恢复执行

在上述代码中,当触发程序最后的 panic() 函数时,将触发 defer 语句执行,defer 中的 panic() 函数将覆盖之前的 panic() 函数,并被 recover() 函数捕获。

相关文章