陈大剩博客

Golang 并发编程(五):Golang 中 defer、panic、recover 语句详解

  • 陈大剩
  • 2025-02-05 22:00:57
  • 7

golang 标题

defer

在 Go 语言中,defer 用于延迟调用指定函数,它只能出现在函数内部,由 defer 关键字以及某个函数的调用表达式组成。我们可以理解 defer 调入一个函数 执行栈 的过程,然后再依次取出。

简单例子如下:

func printStr() {
    defer fmt.Println("函数执行结束前一刻才会被打印。")
    fmt.Println("第一个被打印的。")
}

其中 defer 关键字后面是针对 fmt.prnintln 函数的通用表达式。上面 printStr 为外围函数,调用 printStr 的函数为调用函数

defer 具体规则如下:

  • 当外围函数中的语句正常执行完毕时,只有其中所有的延迟函数都执行完毕,外围函数才会真正结束执行;
  • 当执行外围函数中的 return 语句时,只有其中的所有延迟函数都执行完毕后,外围函数才会真正返回;
  • 当外围函数引发运行时恐慌(panic)时,只有其中所以的延迟函数都执行完毕后,该运行恐慌才会真正扩散至调用函数;

因为 defer 语句这样的特性,所以成为执行释放资源或异常处理等收尾任务的首选,明显的优势有两个,如下:

  • 对延迟函数的调用总会在外围函数执行结束前执行
  • defer 语句在外围函数体中位置不限,并且数量不限。

需要注意的三点,如下:

第一点:使用 defer 延迟函数,外围函数中执行顺序不同。

func printNumbers() {
    for i := 0; i < 10; i++ {
       defer func(n int) {
          fmt.Println(n)
       }(i)
    }
}

这里会输出 9876543210

第二点:同一个外围函数多个延迟函数调用的执行顺序不同。

func printNumbers2() {
    for i := 0; i < 10; i++ {
        defer fmt.Println(i)
        defer fmt.Println(i * 2)
    }
}

第三点:延迟函数调用传入参数时,会在当前 defer 语句执行时求出。

func printNumbers3() {
    for i := 0; i < 5; i++ {
       defer func(n int) {
          fmt.Printf("%d \n", n)
       }(i * 2)
    }
}

也就是说 i*2 会当前 defer 语句执行时求出,最后再依次调出

panic

写过 Go 的朋友都知道,Go 在报告错误的方式是返回一个 error 类型的值,遇到致命错误时,很有可能会使程序无法继续运行。这时我们可以使用 panic,为了控制运行期间的致命错误,Go 内建了 专用函数 panic,该函数用于停止当前的控制流程并引发一个恐慌。

具体运行如下

func main() {
    output()
}

func output() {
    inner()
}

func inner() {
    panic(errors.New("预定义故障"))
}

panic 可以接受一个任意类型的参数值,不过这个参数类型常常会是 string 或者 error,这样更容易显示运行时的恐慌。

panic 也可以是运行时系统来引发,例如:

func main() {
    index := 3
    ay := [2]int{1, 2}
    _ = ay[index]
}

注意:此处相当于显式调用 panic 并传入一个 runtime.Error 类型的参数。

recover

我们都不希望程序崩溃,那么 recover 函数 就派上用场了,recover 可以 “拦截” 一个运行时恐慌。运行时恐慌一旦被引发,就会向调用方传播直至程序崩溃,recover 用于从当前恐慌状态中恢复并重新获得流程控制权,recover 函数被调用后,会返回一个 interface{} 类型的结果,如果当时程序正在处于恐慌状态,那么这个结果就会是非 nil 的。

recover 函数一般应该与 defer 语句配合起来使用,例如:

defer func() {
    if err := recover(); err != nil {
       fmt.Println(err)
    }
}()

把此类代码放在函数体开始处,这样可以有效防止该函数及其下层调用中的代码引发运行时恐慌,一旦发现 recover 函数调用结果非 nil ,就立刻采取措施。

func main() {
    safeDivision(10, 0) // 触发恐慌
    fmt.Println("程序继续执行")
}
func safeDivision(a, b int) {
    defer func() {
       if r := recover(); r != nil {
          fmt.Println("恢复了恐慌:", r)
       }
    }()

    if b == 0 {
       panic("除以零错误") // 引发恐慌
    }

    fmt.Println("结果:", a/b)
}

代码解释:defer 语句会延迟到最后执行,而 recover() 函数会捕获运行时恐慌,所以这里程序会继续执行。

分享到:
0

说点儿什么吧

头像

表情

本站由陈大剩博客程序搭建 | 湘ICP备2023000975号| Copyright © 2017 - 陈大剩博客 | 本站采用创作共用版权:CC BY-NC 4.0

站长统计| 文章总数[123]| 评论总数[11]| 登录用户[26]| 时间点[127]

logo

登入

社交账号登录