Go defer 知识点

347 阅读3分钟

在使用 Golang 开发程序时,我们可能会经常使用 defer 关键字,实现资源的回收,以及完成“收尾”工作。

执行顺序

若函数中有多个 defer ,执行顺序为 先进后出 ,可理解为栈。

package main

import (
	"fmt"
)

func main() {
	for i := 0; i < 3; i++ {
		defer fmt.Println(i)
	}
}

// 2
// 1
// 0

参数确定的时机

defer 在声明时会先计算确定其参数的值,推迟执行的仅是其函数体。

package main

import "fmt"

func print(a int) {
	fmt.Println("value in deferred function", a)
}

func main() {
	a := 5
	defer print(a)
	a = 10
	fmt.Println("value before deferred function call", a)
}

// value before deferred function call 10
// value in deferred function 5

在实际开发中,defer 的使用经常伴随着闭包和匿名函数的使用。

package main

import "fmt"

func main() {
	for i := 0; i < 3; i++ {
		// i 在每次循环中,都被确定其对应的值:0,1,2
		defer fmt.Println(i)
	}
}

// 2
// 1
// 0
package main

import "fmt"

func main() {
	for i := 0; i < 3; i++ {
		// 由于函数体只是被延迟执行,故在执行 defer 时,
		// i 的值为 3
		defer func() {
			fmt.Println(i)
		}()
	}
}

// 3
// 3
// 3

return

在调用 return 时,会完成以下的事情:

  • 对返回值赋值
  • 调用 defer 表达式
  • 退出函数,并将返回值给调用函数
package main

import (
	"fmt"
)

// 匿名返回值
func func_naked_return() int {
	defer func() {
		fmt.Println("由于无法获取到匿名返回值变量,故无法对其进行修改")
	}()
	fmt.Println("对返回值赋值")
	return 1
}

// 匿名返回值
func func_naked_return_pointer() *int {
	var r int
	defer func() {
		r++
		fmt.Println("由于返回类型为指针,故可通过修改 r 间接修改匿名返回值")
	}()
	fmt.Println("对返回值赋值")
	return &r
}

// 命名返回值
func func_named_return() (r int) {
	defer func() {
		fmt.Println("在调用 defer 表达式时,已完成对返回值的赋值 ", r)
		r = 2
	}()
	fmt.Println("对返回值赋值")
	return 1
}

func main() {
	fmt.Println("退出函数,将返回值给调用函数 ", func_naked_return())
	fmt.Println("-------")
	fmt.Println("退出函数,将返回值给调用函数 ", func_named_return())
	fmt.Println("-------")
	fmt.Println("退出函数,将返回值给调用函数 ", *func_naked_return_pointer())
}

// 对返回值赋值
// 由于无法获取到匿名返回值变量,故无法对其进行修改
// 退出函数,将返回值给调用函数  1
// -------
// 对返回值赋值
// 在调用 defer 表达式时,已完成对返回值的赋值  1
// 退出函数,将返回值给调用函数  2
// -------
// 对返回值赋值
// 由于返回类型为指针,故可通过修改 r 间接修改匿名返回值
// 退出函数,将返回值给调用函数  1
  • 匿名返回值变量无法被 defer 表达式所获取,故 defer 无法直接修改匿名返回值。但在返回值为指针类型的情况下,可通过修改指针所指向的值,实现间接修改匿名返回值

作用域

  • 当任意一条(主)协程发生 panic 时,会执行当前协程中 panic 之前已声明的 defer
  • 主动调用 os.Exit(int) 时,defer 将不执行
  • 在发生 panic 的(主)协程中,若没有一个 defer 调用 recover() 进行修复,则在执行完之前已声明的 defer 后,进程崩溃
  • defer 只对当前的(主)协程有效

参考文档