defer的go生三大问

what?

defer 是go语言中的一种用于在函数中执行完成后执行指定逻辑的机制。

why?

在很多场景中,使用defer既可以增加代码的可读性,又可以防止开发人员开发过程中漏掉一些需要代码执行逻辑执行完才需要执行的逻辑,比如:

  1. 锁的 unlock
  2. 数据流的 close()
  3. 事务的 commit or rollback

how?

举一个最简单的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main
import "fmt"
func df()(x int){
b := 0
defer func(){
b++
}()
defer func(){
x = b
}()
defer func(){
b+=2
}()
return
}
func main(){
fmt.Println(df())
}

输出结果为: 2

分析原因:
defer关键字相当于把待执行函数放入一个先进后出的栈中,当执行完整个函数,开始执行defer中的函数。b+=2 -> x=b -> b++ ;因此,输出结果为2

defer的发展

最近发布的1.14 defer性能提升很多,相比于1.12提升近十倍效率,以下是为本地测试代码及结果:

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
package test
import (
"testing"
)
func d(){}
func withDefer_2(i int) {
if i > 0{i--}
defer d()
}
func withDefer2_2(i int) {
defer d()
if i > 0{i--}
}
func withoutDefer_2(i int) {
if i > 0{i--}
d()
}
func withIfDefer_2(i int) {
if i>0{i--;defer d()}
}
func BenchmarkWithoutDefer_2(b *testing.B){
num:=10
b.ResetTimer()
for i:=0;i<b.N;i++{
withoutDefer_2(num)
}
}
func BenchmarkWithIfDefer_2(b *testing.B){
num:=10
b.ResetTimer()
for i:=0;i<b.N;i++{
withIfDefer_2(num)
}
}
func BenchmarkWithDefer_2(b *testing.B){
num:=10
b.ResetTimer()
for i:=0;i<b.N;i++{
withDefer_2(num)
}
}
func BenchmarkWithDefer2_2(b *testing.B){
num:=100
b.ResetTimer()
for i:=0;i<b.N;i++{
withDefer2_2(num)
}
}
/**
测试结果:
go v1.14.2:
BenchmarkWithoutDefer_2
BenchmarkWithoutDefer_2-4 1000000000 0.247 ns/op
BenchmarkWithIfDefer_2
BenchmarkWithIfDefer_2-4 347914345 3.43 ns/op
BenchmarkWithDefer_2
BenchmarkWithDefer_2-4 445812684 2.69 ns/op
BenchmarkWithDefer2_2
BenchmarkWithDefer2_2-4 473210992 2.47 ns/op
-
go v1.13.4
BenchmarkWithoutDefer_2-4 1000000000 0.241 ns/op
BenchmarkWithIfDefer_2-4 44776783 28.1 ns/op
BenchmarkWithDefer_2-4 44352997 27.5 ns/op
BenchmarkWithDefer2_2-4 40123170 27.0 ns/op
-
go v1.12.5
BenchmarkWithoutDefer_2-4 2000000000 0.50 ns/op
BenchmarkWithIfDefer_2-4 30000000 37.2 ns/op
BenchmarkWithDefer_2-4 50000000 36.7 ns/op
BenchmarkWithDefer2_2-4 50000000 37.1 ns/op
**/

在无defer的场景中,每次操作执行时间约0.5 ns,在有defer场景中,1.14版本执行时间在2.5-3.5之间,1.12版本执行时间在37左右,相差十倍。1.13版本速度比1.12大概提升30%

不同版本中defer的实现

  1. 在1.12版本中,defer的实现主要是通过 runtime.deferproc 函数,创建defer并存入g的defer栈中,在执行完函数后,会循环调用 runtime.deferreturn函数来执行defer函数
  2. 在1.13版本中,defer的实现是通过执行 runtime.deferprocstack 函数,从栈上创建defer函数,相比于原来在堆上创建,速度提升了很多。
  3. 在1.14版本中,defer的实现主要是编译阶段生成的jmp指令,通过指令跳转,减少函数调用时间,大幅度提升了defer的执行效率。(当函数中defer数量超过8个时,效率会骤减,但效率仍比1.12中高30%左右,超过8个使用1.13中的方法执行defer)。为什么是8个:有一个8位的deferBits,每增加一个defer,deferBits的一个bit被设置为1,编译过程分析到deferBits不够用的时候,会切换defer实现方式,当使用for循环defer时,会采用1.12版本的方法将defer入栈,for外的使用1.13版本方法入栈。具体可以写示例代码,编译汇编代码查看
  4. panic场景,编译器会在可能发生panic的地方加入汇编,判断panic条件