以下基于 Go 1.14
Go 语言中的 defer
常用来进行资源释放。它有以下几个特点: * 向 defer
传入的函数会在当前函数或者方法返回之前运行。 * 函数中调用的多个 defer
会以先调用后执行的方式进行 * 在调用 defer
时,就会对函数传入的参数进行计算。
defer
类型
有三种类型的 defer
编译器的 ssa 过程中会确认当前 defer
的类型: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19// compile.internal.gc.state.stmt
func (s *state) stmt(n *Node) {
//...
switch n.Op {
// ...
case ODEFER:
// ...
if s.hasOpenDefers {
s.openDeferRecord(n.Left)
} else {
d := callDefer
if n.Esc == EscNever {
d = callDeferStack
}
s.call(n.Left, d)
}
}
// ...
}$ go tool compile -d defer hello.go
来检查 defer
类型。
open-coded
Go 1.14 引入,目的是优化 defer 的运行时间。编译器在 ssa 过程中,会将被延迟的方法直接插入到函数的尾部(inline),从而避免运行时的 deferproc
和 deferprocStack
操作,以及多次调用 deferreturn
。
- 以下情况不使用这种类型来处理
defer
:- 函数中对
defer
的调用次数超过 8(这是为了最小化代码大小,只使用 1 个 byte 来辅助标识)(例如下面的f0
和f1
) - 函数中存在出现在循环中的
defer
(例如下面的f2
、f3
、f4
和f5
)- 包括使用
for
构造的和使用 label+goto
构造的
- 包括使用
- 函数中出现过多(返回语句次数 * defer 个数 > 15)的返回语句
- 因为会在每个返回点前插入被 defer 的函数调用
gcflags
无 N
- 函数中对
举例说明: 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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95// 8 次 defer
func f0() {
defer func() { // open-coded defer
fmt.Println("defer0")
}()
defer func() { // open-coded defer
fmt.Println("defe1")
}()
defer func() { // open-coded defer
fmt.Println("defer2")
}()
defer func() { // open-coded defer
fmt.Println("defe3")
}()
defer func() { // open-coded defer
fmt.Println("defer4")
}()
defer func() { // open-coded defer
fmt.Println("defe5")
}()
defer func() { // open-coded defer
fmt.Println("defer6")
}()
defer func() { // open-coded defer
fmt.Println("defer7")
}()
fmt.Println("f0")
}
func f1() { // 9 次 defer
defer func() { // stack-allocated defer
fmt.Println("defer0")
}()
defer func() { // stack-allocated defer
fmt.Println("defe1")
}()
defer func() { // stack-allocated defer
fmt.Println("defer2")
}()
defer func() { // stack-allocated defer
fmt.Println("defe3")
}()
defer func() { // stack-allocated defer
fmt.Println("defer4")
}()
defer func() { // stack-allocated defer
fmt.Println("defe5")
}()
defer func() { // stack-allocated defer
fmt.Println("defer6")
}()
defer func() { // stack-allocated defer
fmt.Println("defer7")
}()
defer func() { // stack-allocated defer
fmt.Println("defer8")
}()
fmt.Println("f1")
}
func f2() { // defer 没有出现在循环中(for)
defer func() { // open-coded defer
fmt.Println("defer0")
}()
for i := 0; i < 1; i += 1 {
fmt.Println("f2", i)
}
}
func f3() { // defer 出现在循环中(for)
for i := 0; i < 1; i += 1 {
defer func() { // heap-allocated defer
fmt.Println("defer0")
}()
}
fmt.Println("f3")
}
func f4() { // defer 没有出现在循环中(label+goto)
defer func() { // open-coded defer
fmt.Println("defer0")
}()
label:
fmt.Println("f4")
goto label
}
func f5() { // defer 出现在循环中(label+goto)
label:
defer func() { // heap-allocated defer
fmt.Println("defer0")
}()
fmt.Println("f5")
goto label
}defer
相关的结构体
那么,什么时候会在栈上分配呢?答案在下面这部分代码: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// src/cmd/compile/internal/gc/escape.go
func (e *Escape) augmentParamHole(k EscHole, call, where *Node) EscHole {
// ...
// Top level defers arguments don't escape to heap, but they
// do need to last until end of function. Tee with a
// non-transient location to avoid arguments from being
// transiently allocated.
if where.Op == ODEFER && e.loopDepth == 1 {
// force stack allocation of defer record, unless open-coded
// defers are used (see ssa.go)
where.Esc = EscNever
return e.later(k)
}
// ...
}1
2
3
4
5
6
7
8
9
10
11func f6() {
defer func() { // stack-allocated defer
fmt.Println("defer2")
}()
for {
defer func() { // heap-allocated defer
fmt.Println("defer1")
}()
break
}
}defer
相关的结构体,最原始的方式。
实现原理
一个数据结构
在 Go 中,defer
关键字对应的数据结构为 runtime._defer
。这是一个用链表实现的栈。
编译时
- 处理
defer
关键字- 如果是
open-coded
类型的 defer,则调用cmd/compile/internal/gc.state.openDeferRecord
方法, - 如果是
stack-allocated
类型,则转换成runtime.deferprocStack
- 如果是
heap-allocated
类型,则转换成runtime.deferproc
- 如果是
- 在调用
defer
的函数返回之前插入runtime.deferreturn
### 运行时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// compile.internal.gc.state
// 处理任何需要在返回前生成的代码
func (s *state) exit() *ssa.Block {
if s.hasdefer { // 函数中存在 defer 调用
if s.hasOpenDefers { // 如果有 open-coded 类型的 defer
if shareDeferExits && s.lastDeferExit != nil && len(s.openDefers) == s.lastDeferCount {
if s.curBlock.Kind != ssa.BlockPlain {
panic("Block for an exit should be BlockPlain")
}
s.curBlock.AddEdgeTo(s.lastDeferExit)
s.endBlock()
return s.lastDeferFinalBlock
}
s.openDeferExit()
} else { // 对于其他类型的 defer,调用
s.rtcall(Deferreturn, true, nil)
}
}
//...
}
// openDeferExit 生成 SSA,从而在退出的时候处理所有的 open-coded 类型的 defer。
// 这个过程会加载 deferBits 字段,然后检查这个字段的每个位,检查是否执行了对应的 defer 语句。
// 对于每一个打开的位,会进行相关的 defer 调用。
func (s *state) openDeferExit() {
// ...
} - 如果调用了
runtime.deferprocStack
或者runtime.deferproc
,那么它们都会将一个新的runtime._defer
结构体(此时就会对函数参数进行计算)追加到当前 Goroutine 的_defer
链表的头部 runtime.deferreturn
会从 Goroutine 的_defer
链表中取出runtime._defer
结构并执行- 如果是
open-coded
类型的延迟调用,则会调用runtime.runOpenDeferFrame
函数来运行该_defer
结构中所有有效的延迟调用。 - 否则,它会调用
runtime·jmpdefer
函数。这个函数会跳到对应被延迟调用的函数并执行 - 会多次调用
runtime.deferreturn
,直到所有的延迟调用都执行完毕。
- 如果是