README ¶
golang defer tips
A defer statement pushes a function call onto a list. The list of saved calls is executed after the surrounding function returns. Defer is commonly used to simplify functions that perform various clean-up actions.
defer表达式将一个函数调用保存在列表中,当包裹defer的函数"返回"后,列表中的调用会被执行。defer通常用于清理收尾工作。
0-1-unamed-return
➜ go run 0-1-unamed-return.go
defer1: 1
defer2: 2
return: 0
func a() int {
var i int
defer func() {
i++
fmt.Println("defer2:", i) // 打印结果为 defer: 2
}()
defer func() {
i++
fmt.Println("defer1:", i) // 打印结果为 defer: 1
}()
return i
}
函数a() 返回列表中只声明了返回的数据类型,并没声明变量, return 返回时首先将返回值i 赋值给返回变量,此处a()没有,golang语言会在此时先为其声明一个返回变量假如为j , 则return 先将i的值赋值给j(注意这里是值传递操作,而非引用操作), 然后交给defer 做清理动作,当defer 清理完成后 返回值j 才会给下一步代码调用; 这里可以看到defer清理过程改变的是变量i, 而j不会被改变;
0-2-named-return
➜ go run 0-2-named-return.go
defer1: 1
defer2: 2
return: 2
func b() (i int) {
defer func() {
i++
fmt.Println("defer2:", i) // 打印结果为 defer: 2
}()
defer func() {
i++
fmt.Println("defer1:", i) // 打印结果为 defer: 1
}()
return i // 或者直接 return 效果相同
}
函数b()返回列表中声明了返回变量为i, 所以在return赋值之后,defer中的修改也是在修改这个返回变量i, 所以最终i的值是两次defer修改之后的值;
0-3-ptr-return
➜ go run 0-3-ptr-return.go
c defer1: 1
c defer2: 2
c return: 2
func c() *int {
var i int
defer func() {
i++
fmt.Println("c defer2:", i) // 打印结果为 c defer: 2
}()
defer func() {
i++
fmt.Println("c defer1:", i) // 打印结果为 c defer: 1
}()
return &i
}
函数c() 返回列表中也没有声明变量,与函数a()不同的是 函数c()返回的是地址引用值, 所以当return之后defer的清理操作修改的还是返回变量指向的地址空间中的值,所以最终返回变量的值也因为地址空间值被defer修改而改变;
0-4-compilation-analysis
func a() int {
var i int
defer func() {
i++
}()
return i
}
编译成汇编指令
go build -gcflags "-N -l" -ldflags=-compressdwarf=false -o 0-4-compilation-analysis.out 0-4-compilation-analysis.go
go tool objdump -s "main.a" 0-4-compilation-analysis.out > 0-4-compilation-analysis.S
go tool objdump -s 参数"main.a" 表示指抓取namespace main 下a函数执行的汇编指令
0-4-compilation-analysis.go:12 0x109d026 e805faf8ff CALL runtime.deferprocStack(SB)
0-4-compilation-analysis.go:12 0x109d02b 85c0 TESTL AX, AX
0-4-compilation-analysis.go:12 0x109d02d 751c JNE 0x109d04b
0-4-compilation-analysis.go:12 0x109d02f eb00 JMP 0x109d031
0-4-compilation-analysis.go:15 0x109d031 488b442408 MOVQ 0x8(SP), AX
0-4-compilation-analysis.go:15 0x109d036 4889442470 MOVQ AX, 0x70(SP)
0-4-compilation-analysis.go:15 0x109d03b 90 NOPL
0-4-compilation-analysis.go:15 0x109d03c e84f02f9ff CALL runtime.deferreturn(SB)
0-4-compilation-analysis.go:15 0x109d041 488b6c2460 MOVQ 0x60(SP), BP
0-4-compilation-analysis.go:15 0x109d046 4883c468 ADDQ $0x68, SP
0-4-compilation-analysis.go:15 0x109d04a c3 RET
0-4-compilation-analysis.go:12 0x109d04b 90 NOPL
0-4-compilation-analysis.go:12 0x109d04c e83f02f9ff CALL runtime.deferreturn(SB)
0-4-compilation-analysis.go:12 0x109d051 488b6c2460 MOVQ 0x60(SP), BP
0-4-compilation-analysis.go:12 0x109d056 4883c468 ADDQ $0x68, SP
0-4-compilation-analysis.go:12 0x109d05a c3 RET
0-4-compilation-analysis.go:10 0x109d05b e820c3fbff CALL runtime.morestack_noctxt(SB)
0-4-compilation-analysis.go:10 0x109d060 e96bffffff JMP main.a(SB)
上述0-4-compilation-analysis.go:12 是文件名:行号, 可以清晰的看到代码中的某一行对应的汇编指令集;
0-4-compilation-analysis.go第12行defer func(){} 汇编指令集进行了CALL runtime.deferprocStack(SB)操作, 将defer 入栈;
第15行调用CALL runtime.deferreturn(SB) 执行return ,紧接着回到第12行 执行之前入栈的defer 函数;
当前main.a()函数调用结束,打印 CALL runtime.morestack_noctxt(SB) 表示当前main.a()上下文栈中以全部出栈,即最后跳转到第10行;
1-1-unamed-return
➜ go run 1-1-unamed-return.go
golang
panic: unexpected EOF
goroutine 1 [running]:
main.main()
/Users/lihong/workbench/dev/src/github.com/researchlab/gbp/defer/1-1-unamed-return.go:17 +0x280
exit status 2
func gzipFlush(data []byte) bytes.Buffer {
var b bytes.Buffer
w := gzip.NewWriter(&b)
defer w.Close()
w.Write(data)
w.Flush()
return b
}
flush() 返回的不是基本数据类型,而是结构体类型, 同样return 将b的值赋值给一个新的bytes.Buffer类型变量(注意这里是值传递而非引用), 之后虽然defer w.Close() 关闭了gzip.Writer类型,但此时defer影响的对象是b变量,而之前值传递操作的返回值变量不受此时的defer影响,所以导致最终在ioutil.ReaddAll(b)时无法正常读取到
End-of-File
而触发panic: unexpected EOF;
1-2-named-return
func flush(data []byte) (b bytes.Buffer) {
w := gzip.NewWriter(&b)
defer w.Close()
w.Write(data)
w.Flush()
return b
}
有名返回, defer修改直接影响到返回值,所以最终ioutil.RealAll()可以正确读取到EOF
1-3-ptr-return
func flush(data []byte) *bytes.Buffer {
var b bytes.Buffer
w := gzip.NewWriter(&b)
defer w.Close()
w.Write(data)
w.Flush()
return &b
}
指针返回值,return之后的defer操作修改也能直接影响返回值变量,因为是引用操作;
1-4-no-defer
func flush(data []byte) bytes.Buffer {
var b bytes.Buffer
w := gzip.NewWriter(&b)
w.Write(data)
w.Flush()
w.Close()
return b
}
如果不使用defer, 只要在return之前 记得w.Close() 自然也不存在ioutil.ReadAll()读取不到EOF;
1-5-determined-at-declaration
func calc(index string, a, b int) int {
ret := a + b
fmt.Println(index, a, b, ret)
return ret
}
func main() {
a := 1
b := 2
defer calc("1", a, calc("10", a, b))
a = 0
defer calc("2", a, calc("20", a, b))
b = 1
}
defer的参数在声明时即被确定下来, defer calc("1", a, calc("10", a, b)) 的第3个参数会在调用runtime.deferproc时确定,并不会在延时调用时才会被计算。
defer 与 return 在一起的执行流程
step 1 : 在defer表达式的地方,会调用runtime.deferproc(size int32, fn *funcval)保存延时调用,注意这里保存了延时调用的参数 step 2 : 在return时,先将返回值保存起来 step 3 : 按FILO顺序调用runtime.deferreturn,即延时调用 step 4 : RET指令
1-6,1-7 execution-sequence
func main() {
// defer 是针对 tfunc 返回的函数
defer tfunc("main10")()
go demo()
time.Sleep(3 * time.Second)
fmt.Println("main20")
}
func demo() {
defer trace("demo00")
fmt.Println("demo01")
}
func tfunc(msg string) func() {
fmt.Println("start ", msg)
return func() { fmt.Println("tfunc end") }
}
output
start main10
demo01
enter demo00
main20
tfunc end
defer 作用于tfunc返回的函数
1-8 panic
func main() {
demo()
}
func demo() {
defer func() {
if r := recover(); r != nil {
fmt.Println("recover:", r)
}
}()
defer fmt.Println("demo")
panic("panic")
defer fmt.Println("finished")
}
output
demo
recover: panic
defer在panic 之前执行, panic时,会先执行完已注册的defer 函数; recover会捕获panic;
Documentation ¶
There is no documentation for this package.