defer is a keyword provided by golang, which is called after the function or method completes execution and returns.
Each defer will push the defer function into the stack. When the function or method is called, it will be taken out from the stack for execution. Therefore, the execution order of multiple defers is first in, last out.
for i := 0; i <= 3; i++ { defer fmt.Print(i) } //输出结果时 3,2,1,0
defer trigger timing
The official website makes it very clear:
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.
- When the function wrapped in the defer statement returns
- When the function wrapped in the defer statement is executed to the end
When the current goroutine panics
//输出结果:return前执行defer func f1() { defer fmt.Println("return前执行defer") return } //输出结果:函数执行 // 函数执行到最后 func f2() { defer fmt.Println("函数执行到最后") fmt.Println("函数执行") } //输出结果:panic前 第一个defer在Panic发生时执行,第二个defer在Panic之后声明,不能执行到 func f3() { defer fmt.Println("panic前") panic("panic中") defer fmt.Println("panic后") }
Copy after login
defer, return, return value execution order
Let’s look at 3 examples first
func f1() int { //匿名返回值 var r int = 6 defer func() { r *= 7 }() return r } func f2() (r int) { //有名返回值 defer func() { r *= 7 }() return 6 } func f3() (r int) { //有名返回值 defer func(r int) { r *= 7 }(r) return 6 }
The execution result of f1 is 6, the execution result of f2 is 42, the execution result of f3 is 6
In the official document of golang It introduces the execution order of return, defer, and return value:
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 .
1. Assign the return value first
2. Execute the defer statement
3. Wrap the function return to return
The result of f1 is 6. f1 is an anonymous return value. The anonymous return value is declared when return is executed. Therefore, when defer is declared, the anonymous return value cannot be accessed. Modification of defer will not affect the return value.
f2 first assigns the return value r, r=6, executes the defer statement, defer modifies r, r = 42, and then the function returns.
f3 is a named return value, but because r is used as a parameter of defer, when declaring defer, the parameters are copied and passed, so defer will only affect the local parameters of the defer function and will not affect the calling function. The return value.
Closures and anonymous functions
Anonymous function: A function without a function name.
Closure: A function that can use variables in the scope of another function.
for i := 0; i <= 3; i++ { defer func() { fmt.Print(i) } } //输出结果时 3,3,3,3 因为defer函数的i是对for循环i的引用,defer延迟执行,for循环到最后i是3,到defer执行时i就 是3 for i := 0; i <= 3; i++ { defer func(i int) { fmt.Print(i) }(i) } //输出结果时 3,2,1,0 因为defer函数的i是在defer声明的时候,就当作defer参数传递到defer函数中
defer source code analysis
The implementation source code of defer is in runtime.deferproc
Then run the function runtime.deferreturn before the function returns.
First understand the defer structure:
type _defer struct { siz int32 started bool sp uintptr // sp at time of defer pc uintptr fn *funcval _panic *_panic // panic that is running defer link *_defer }
sp and pc point to the stack pointer and the caller's program counter respectively, fn is the function passed into the defer keyword, and Panic is the Panic that causes defer to be run. .
Every time a defer keyword is encountered, the defer function will be converted into runtime.deferproc
deferproc creates a delay function through newdefer, and hangs this new delay function on the current goroutine's _defer linked list
func deferproc(siz int32, fn *funcval) { // arguments of fn follow fn sp := getcallersp() argp := uintptr(unsafe.Pointer(&fn)) + unsafe.Sizeof(fn) callerpc := getcallerpc() d := newdefer(siz) if d._panic != nil { throw("deferproc: d.panic != nil after newdefer") } d.fn = fn d.pc = callerpc d.sp = sp 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)) } return0() }
newdefer will first take out a _defer structure from the deferpool of sched and current p. If the deferpool does not have _defer, a new _defer will be initialized.
_defer is associated with the current g, so defer is only valid for the current g.
d.link = gp._defer
gp._defer = d //Use a linked list to connect all defers of the current g
func newdefer(siz int32) *_defer { var d *_defer sc := deferclass(uintptr(siz)) gp := getg() if sc < uintptr(len(p{}.deferpool)) { pp := gp.m.p.ptr() if len(pp.deferpool[sc]) == 0 && sched.deferpool[sc] != nil { ..... d := sched.deferpool[sc] sched.deferpool[sc] = d.link d.link = nil pp.deferpool[sc] = append(pp.deferpool[sc], d) } if n := len(pp.deferpool[sc]); n > 0 { d = pp.deferpool[sc][n-1] pp.deferpool[sc][n-1] = nil pp.deferpool[sc] = pp.deferpool[sc][:n-1] } } ...... d.siz = siz d.link = gp._defer gp._defer = d return d }
deferreturn Take out the _defer linked list from the current g and execute it, each _defer call freedefer releases the _defer structure and puts the _defer structure into the deferpool of the current p.
defer performance analysis
defer is very useful in development for releasing resources, capturing Panic, etc. It is possible that some developers have not considered the impact of defer on program performance and abuse defer in their programs.
It can be found in the performance test that defer still has some impact on performance. Yuchen's Go performance optimization tips 4/1, there are some tests on the extra overhead caused by defer statements.
Test code
var mu sync.Mutex func noDeferLock() { mu.Lock() mu.Unlock() } func deferLock() { mu.Lock() defer mu.Unlock() } func BenchmarkNoDefer(b *testing.B) { for i := 0; i < b.N; i++ { noDeferLock() } } func BenchmarkDefer(b *testing.B) { for i := 0; i < b.N; i++ { deferLock() }
Test result:
BenchmarkNoDefer-4 100000000 11.1 ns/op BenchmarkDefer-4 36367237 33.1 ns/op
It can be known from the previous source code analysis that defer will first call deferproc , these will copy parameters, and deferreturn will also extract relevant information and delay execution. These are more expensive than directly calling a statement.
The performance of defer is not high. Each defer takes 20ns. If it occurs multiple times in a func, the performance consumption is 20ns*n. The cumulative waste of CPU resources is very large.
Solution: Except when exception capture is required, defer must be used; for other resource recycling defers, you can use goto to jump to the resource recycling code area after judging failure. For competitive resources, you can release the resources immediately after use, so that the competitive resources can be optimally used.