Golang学习 - defer延迟调用
hanpy

Go 语言的 defer 会在当前函数返回前执行传入的函数,它会经常被用于关闭文件描述符、关闭数据库连接以及解锁资源。

defer关键字的常见使用场景

资源释放

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func CopyFile(dstName, srcName string) (written int64, err error) {
src, err := os.Open(srcName)
if err != nil {
return 0, err
}
defer src.Close()

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

捕获错误
defer的特性是无论后续函数的执行路径如何以及是否发生了panic,在函数结束后一定会得到执行,所以用来捕获错误就很适合了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import "fmt"

func main() {
executeOp()
fmt.Println("main end")
}

func executeOp() {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
fmt.Println("recover func msg")
}()
panic("executeOp func panic msg")
fmt.Println("executeOp end")
}
// executeOp func panic msg
// recover func msg
// main end

defer使用时的注意事项

执行顺序
在同一个函数中,defer函数调用的执行顺序与它们分别所属的defer语句的出现顺序(更严谨地说,是执行顺序)完全相反。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import "fmt"

func main() {
for i := 0; i < 5; i++ {
defer fmt.Println(i)
}
fmt.Println("main stop")
}
// 4
// 3
// 2
// 1
// 0

参数的预计算
函数到达defer语句时,延迟调用的参数将立 即求值,传递到defer函数中的参数将预先被固定,而不会等到函数执 行完成后再传递参数到defer中。

1
2
3
4
5
6
7
8
9
func main() {
i := 10
defer fmt.Printf("defer:%d\n", i)

i++
defer fmt.Printf("main:%d\n", i)
}
// main:11
// defer:10

想要解决这个问题的方法非常简单,我们只需要向defer关键字传入匿名函数:虽然调用defer关键字时也使用值传递,但是因为拷贝的是函数指针,所以会打印出符合预期的结果。

1
2
3
4
5
6
7
8
9
10
11
func main() {
i := 10
defer func() {
fmt.Printf("defer:%d\n", i)
}()

i++
defer fmt.Printf("main:%d\n", i)
}
// main:11
// defer:11

返回值陷阱
还有一种特殊的情况需要注意,就是函数执行完在defer中对返回值修改的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
var g int = 100

func main() {
i := f()
fmt.Printf("main %d \n", i) // 100
}

func f() (r int) {
defer func() {
g = 200
}()
return g
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var g int = 100

func main() {
i := f()
fmt.Printf("main %d \n", i) // 200
}

func f() (r int) {
r = g
defer func() {
r = 200
}()
return r
}

出现上面的情况是因为return并不是原子的,包含下面几步,
将返回值保存在栈上→执行defer函数→函数返回

1
2
3
4
5
6
7
8
9
10
11
12
第一种情况
g=100
r=g
g=200
return

第二种情况
g=100
r=g
r=200
r=200
return

defer的数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type _defer struct {
siz int32 // includes both arguments and results
started bool
heap bool
openDefer bool
sp uintptr // sp at time of defer
pc uintptr // pc at time of defer
fn *funcval // can be nil for open-coded defers
_panic *_panic // panic that is running defer
link *_defer
fd unsafe.Pointer // funcdata for the function associated with the frame
varp uintptr // value of varp for the stack frame
framepc uintptr
}
  • siz 是参数和结果的内存大小;
  • sppc 分别代表栈指针和调用方的程序计数器;
  • fndefer 关键字中传入的函数;
  • _panic 是触发延迟调用的结构体,可能为空;
  • openDefer 表示当前 defer 是否经过开放编码的优化;

runtime._defer结构体是延迟调用链表上的一个元素,所有的结构体都会通过 link 字段串联成链表。

image