defer
在函数中,程序员经常需要创建资源(比如:数据库连接、文件句柄、锁等),为了在函数执行完毕后,及时的释放资源,Go的设计者提供defer(延时机制)
简单的demo
1 | package main |
输出:
number3 res = 30
number2 n2 = 20
number1 n1 = 10
number4 res= 30
- 当
go
执行到一个defer
时,不会立即执行defer
后的语句,而是将defer
后的语句压入到一个”栈”(比喻)中, 然后继续执行函数下一个语句。 当函数执行完毕后,在从
defer
栈中,依次从栈顶取出语句执行(注:遵守栈先入后出的机制)在defer将语句放入到栈时,也会将相关的值拷贝同时入栈
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30package main
import "fmt"
/*
输出:
number3 res = 30
number2 n2 = 20
number1 n1 = 10
number4 res= 30
*/
func main() {
res := sum(10, 20)
fmt.Println("number4 res=", res)
}
func sum(n1 int, n2 int) int {
// 函数执行完毕后,及时的释放资源
// 先进后出
defer fmt.Println("number1 n1 = ", n1)
defer fmt.Println("number2 n2 = ", n2)
// 增加一段
n1++
n2++
res := n1 + n2
fmt.Println("number3 res = ", res) // 最先执行
return res
}
输出:
number3 res = 32 // 输出结果为32
number2 n2 = 20 // 栈中的数值仍是存入前的数值
number1 n1 = 10
number4 res= 32
使用defer+recover来处理错误
1 | package main |
输出:
err= runtime error: integer divide by zero
main()下面的代码...
defer在Go中的数据结构
1 | type _defer struct { |
runtime._defer
结构体是延迟调用链表上的一个元素,所有的结构体都会通过 link
字段串联成链表。
- siz 是参数和结果的内存大小;
- sp 和 pc 分别代表栈指针和调用方的程序计数器;
- fn 是 defer 关键字中传入的函数;
- _panic 是触发延迟调用的结构体,可能为空;
除了上述的这些字段之外,runtime._defer
中还包含一些垃圾回收机制使用的字段,这里为了减少理解的成本就都省去了
小结
defer
关键字的实现主要依靠编译器和运行时的协作
编译期;
将 defer
关键字被转换runtime.deferproc
;
在调用defer
关键字的函数返回之前插入runtime.deferreturn;
运行时:
runtime.deferproc
会将一个新的 runtime._defer
结构体追加到当前Goroutine
的链表头;runtime.deferreturn
会从 Goroutine
的链表中取出runtime._defer
结构并依次执行;
- 后调用的
defer
函数会先执行:- 后调用的
defer
函数会被追加到Goroutine _defer
链表的最前面; - 运行
runtime._defer
时是从前到后依次执行;
- 后调用的
- 函数的参数会被预先计算;
- 调用
runtime.deferproc
函数创建新的延迟调用时就会立刻拷贝函数的参数,函数的参数不会等到真正执行时计算;
- 调用
更多关于Golang defer的编译过程与运行过程可以参考:
参考链接:https://draveness.me/golang/docs/part2-foundation/ch05-keyword/golang-defer/#533-