Go 语言编程 — defer 关键字

目录

defer 关键字

go 用 defer(推迟)关键字来延迟执行指定的函数,比如:

func a() int {
	defer b()
	return 0
}

函数 b() 发生在 return 0 之后。

defer 是一个面向编译器的声明,会让编译器做两件事:

  1. 编译器会将 defer 声明编译为 runtime.deferproc(fn),这样运行时,会调用 runtime.deferproc,在 deferproc 中将所有 defer 挂到 Goroutine 的 defer 链上;
  2. 编译器会在函数 return 之前,增加 runtime.deferreturn 调用,开始处理前面挂在 defer 链上的所有 defer。

可见,defer 关键字会将指定的函数延迟到函数 return 语句之后执行,包括 Goroutine 的 return。defer 常用来释放数据库连接、文件打开句柄等资源的操作,有类似于 Python with Context 的作用。

defer 的用途之一:释放资源

由于 defer 的延迟特性,defer 常用在函数调用结束之后用于清理相关的资源,比如:

f, _ := os.Open(filename)
defer f.Close()

文件资源的释放会在函数调用结束之后借助 defer 语句自动执行,打开和释放必须相对应。

示例:

func CopyFile(dstName, srcName string) (written int64, err error) {
    src, err := os.Open(srcName)
    if err != nil {
        return
    }
    defer src.Close()

    dst, err := os.Create(dstName)
    if err != nil {
        return
    }
    defer dst.Close()

    return io.Copy(dst, src)
}

defer 的用途之二:执行 recover(恢复)

被 defer 的函数在 return 之后执行,这个时机点正好可以捕获函数抛出的 panic,因而 defer 的另一个重要用途就是执行 recover。

recover 只有在 defer 中使用才更有意义,如果在其他地方使用,由于程序已经调用结束并提前返回而无法有效捕捉错误。

package main

import (
    "fmt"
)

func main() {
    defer func() {
        if ok := recover(); ok != nil {
            fmt.Println("recover")
        }
    }()
    panic("error")
}

注意:defer 要放在 panic 执行之前。

defer 特性

多个 defer 的执行顺序

defer 的本质就是把关键字之后的函数执行压入一个栈(Stack)中延迟执行,所以当具有多个 defer 时,其执行顺序是后进先出(LIFO
)的。

defer func() { fmt.Println("1") }()
defer func() { fmt.Println("2") }()
defer func() { fmt.Println("3") }()

输出顺序是 3、2、1。

被 deferred 函数的参数在 defer 时确定

这是 defer 关键字的特点,一个函数被 defer 时,它的参数就已经确定了,即使 defer 之后参数发生了修改,也不会影响到 defer 时确定的数值。

func a() {
    i := 0
    defer fmt.Println(i)
    i++
    return
}

函数 a() 执行输出的是 0 而不是 1,因为 defer 时 i 的值是 0。

被 defer 的函数可以读取和修改带名称的返回值

被 defer 的函数是在 return 之后执行,可以修改带名称的返回值,例如下述示例中的返回值 i,在 defer 的 c() 中被修改了,所以返回的是 2。

func c() (i int) {
    defer func() { i++ }()
    return 1
}

猜你喜欢

转载自blog.csdn.net/Jmilk/article/details/107449051