Golang 的 defer 关键字使用注意事项

Golang 提供了 defer 关键字,用于函数退出前执行收尾工作,基本的使用方法就不再赘述了。总结了一些可能踩坑的地方。

defer 执行顺序

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

import "fmt"

func TestFunc() {
defer func() {
fmt.Println("A")
}()

defer func() {
fmt.Println("B")
}()

defer func() {
fmt.Println("C")
}()
}

func main() {
TestFunc()
}

执行结果是:

1
2
3
C
B
A

defer 执行先后顺序与代码顺序是相反的,是后进先出的顺序。

defer 与 return 执行顺序

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 deferFunc() int {
fmt.Println("defer func running")
return 0
}

func returnFunc() int {
fmt.Println("return func running")
return 0
}

func TestFunc() int {
defer deferFunc()
return returnFunc()
}

func main() {
TestFunc()
}

执行结果是:

1
2
return func running
defer func running

可以看到 return 语句先执行,defer 的函数后执行。

defer 影响返回值

既然 defer 执行的函数在 return 语句之后执行。那么 defer 是否可以改变函数返回值?来看下面的例子:

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

import "fmt"

func TestFunc() (result int) {
defer func() {
result = 1024
}()

return 2048

}

func main() {
fmt.Println(TestFunc())
}

执行结果是 1024。return 的是 2048,即 defer 确实可以改变函数返回值。再看个类似的例子:

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

import "fmt"

func TestFunc() int {
result := 1024

defer func() {
result = 2048
}()

return result
}

func main() {
fmt.Println(TestFunc())
}

执行结果是 1024。这时 defer func 虽然更新了 result 的值,但并没有对函数返回值造成影响。原因是该函数的 int 类型返回值未命名(即匿名返回值),所以 defer func 无法修改该返回值,只是修改了 result 变量值。

defer 函数预计算参数值

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

import (
"fmt"
"time"
)

func TestFunc() {
startedAt := time.Now()
defer fmt.Println(time.Since(startedAt))

time.Sleep(time.Second)
}

func main() {
TestFunc()
}

这个例子是计算函数的执行时间,sleep 1s 作为模拟,然后执行结果是:199ns,显然这不是预期的结果。期望的执行时间应该是大于 1s 的。那么问题出在哪里?
由于调用 defer 时会预计算函数参数值,即预计算 time.Since(startedAt) 这个参数的值,而不是 sleep 执行完结束后计算的。可以这样改符合预期:

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

import (
"fmt"
"time"
)

func TestFunc() {
startedAt := time.Now()
defer func() {
fmt.Println(time.Since(startedAt))
}()

time.Sleep(time.Second)
}

func main() {
TestFunc()
}

defer 后面跟匿名函数即可,符合我们的期望。执行结果是:1.003861399s。

defer 与 panic

触发 defer 执行的时机有三个:

  • 包裹 defer 的函数遇到 return
  • 包裹 defer 的函数执行到最后
  • 发生 panic

来看下 defer 与 panic 相遇时会发生什么。

发生 panic 时在 defer 中不捕获异常

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

import "fmt"

func TestFunc() {
defer func() {
fmt.Println("A")
}()

defer func() {
fmt.Println("B")
}()

panic("panic occurred")
}

func main() {
TestFunc()
}

执行结果:

1
2
3
4
B
A
panic: panic occurred
... // panic 堆栈信息

发生 panic 时在 defer 中捕获异常

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 TestFunc() {
defer func() {
fmt.Println("A")
}()

defer func() {
fmt.Println("B")
if err := recover(); err != nil {
fmt.Println("catch panic")
}
}()

panic("panic occurred")
}

func main() {
TestFunc()
}

执行结果是:

1
2
3
B
catch panic
A

这个例子执行结果没什么可说的,发生 panic,可以在 defer 函数中捕获处理。

defer 中发生 panic

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

import "fmt"

func TestFunc() {
defer func() {
if err := recover(); err != nil {
fmt.Printf("catch %s panic\n", err)
}
}()

defer func() {
panic("inner")
}()

panic("outer")
}

func main() {
TestFunc()
}

执行结果是:catch inner panic。即外层发生 panic(“outer”),触发 defer 执行,执行 defer 中又发生 panic(“inner”),最后一个 defer 中捕获 panic,捕获到的是最后一个 panic。

另外,尽量不要使用 panic,仅在发生不可恢复的错误或程序启动时发生意外才使用 panic。


Golang 的 defer 关键字使用注意事项
https://www.chendujin.com/posts/96a822e5.html
作者
托马斯
发布于
2022年8月23日
许可协议