定义
根据golang language spec的定义:
A "defer" statement invokes a function whose execution is deferred to the moment the surrounding function returns, either because the surrounding function executed a return statement, reached the end of its function body, or because the corresponding goroutine is panicking.
我们可以得到defer的运行时机有3种。但是这里的the moment the surrounding function returns可能有点迷。别急,后续的说明又补充说明了:
That is, if the surrounding function returns through an explicit return statement, deferred functions are executed after any result parameters are set by that return statement but before the function returns to its caller.
总结一下就是defer在return关键字后,返回给调用方前,按照先进后出的顺序执行。我们用个实际例子来感受下:
package main import ( "fmt" ) func main() { defer func() { fmt.Println("first defer") }() defer func() { fmt.Println("second defer") }() defer func() { fmt.Println("third defer") }() fmt.Println("Hello, playground") } /* Output: Hello, playground third defer second defer first defer */
官方文档还有一段话,通常踩到defer的坑就是因为没有理解这段话:
Each time a "defer" statement executes, the function value and parameters to the call are evaluated as usual and saved anew but the actual function is not invoked. Instead, deferred functions are invoked immediately before the surrounding function returns, in the reverse order they were deferred.
这里有三个要点:
- parameters to the call are evaluated as usual and saved anew:参数被计算并保存
- invoked immediately before the surrounding function returns:在函数return的时候执行,不是代码块
- in the reverse order they were deferred:先defer的后执行
用一个例子来感受一下:
package main import ( "fmt" ) func main() { var i int // note that when i is passed to defer, it is evaluated // and passed to defer. Later modification will not be notified. defer func(i int) { fmt.Printf("get i in defer: %d\n", i) }(i) fmt.Println("before loop") start := 40 for i := 1; i < 3; i++ { start += i // defer is not fired when go out of a block. defer func() { fmt.Printf("defer in loop, current start is: %d\n", start) }() } i = 100 fmt.Println("after loop") fmt.Println("the final answer is: ", answerToUniverse()) } // first set result=41, then in defer set result to 42, return 42 to caller. func answerToUniverse() (result int) { result = 40 defer func() { result++ }() result++ return result } /* Output: before loop after loop the final answer is: 42 defer in loop, current start is: 43 defer in loop, current start is: 43 get i in defer: 0 */
底层结构
type _defer struct { siz int32 // includes both arguments and results link *_defer }
我们可以看到结构体中主要的字段是link,是一个链表。
func deferproc(siz int32, fn *funcval) { ... d := newdefer(siz) if d._panic != nil { throw("deferproc: d.panic != nil after newdefer") } d.link = gp._defer gp._defer = d // <===== 看这里,这两行说明了为啥defer为啥是先入后出,因为相当于是个链表,每次都插到头部。 ... switch siz { case 0: // Do nothing. case sys.PtrSize: *(*uintptr)(deferArgs(d)) = *(*uintptr)(unsafe.Pointer(argp)) default: memmove(deferArgs(d), unsafe.Pointer(argp), uintptr(siz)) // <===== 看这里,deferArgs说明了为啥传入的参数不受后续的改动的影响 } ... } // The arguments associated with a deferred call are stored // immediately after the _defer header in memory. //go:nosplit func deferArgs(d *_defer) unsafe.Pointer { if d.siz == 0 { // Avoid pointer past the defer allocation. return nil } return add(unsafe.Pointer(d), unsafe.Sizeof(*d)) }
第一个函数中中文注释的第一部分说明了为啥defer的顺序是先入后出,
第二部分的注释说明了为啥调用defer传入的参数在之后的新改动在defer函数被调用的时候无法感知。
常用情景
- 释放锁资源
- 统计耗时
- recover panic
总结注意事项
- 多个defer的执行顺序为“后进先出”;
- defer、return、返回值三者的执行逻辑应该是:return最先执行,return负责将结果写入返回值中;接着defer开始执行一些收尾工作;最后函数携带当前返回值退出;
- defer传入的参数时就被计算了,所以有时候后续的改动在执行时无法感知;