本文由go語言教程欄目給大家介紹golang defer實現原理,希望對需要的朋友有所幫助!
defer是golang提供的關鍵字,在函數或者方法執(zhí)行完成,返回之前調用。
每次defer都會將defer函數壓入棧中,調用函數或者方法結束時,從棧中取出執(zhí)行,所以多個defer的執(zhí)行順序是先入后出。
for i := 0; i <= 3; i++ { defer fmt.Print(i) } //輸出結果時 3,2,1,0
defer的觸發(fā)時機
官網說的很清楚:
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語句的函數返回時
- 包裹著defer語句的函數執(zhí)行到最后時
-
當前goroutine發(fā)生Panic時
//輸出結果:return前執(zhí)行defer func f1() { defer fmt.Println("return前執(zhí)行defer") return } //輸出結果:函數執(zhí)行 // 函數執(zhí)行到最后 func f2() { defer fmt.Println("函數執(zhí)行到最后") fmt.Println("函數執(zhí)行") } //輸出結果:panic前 第一個defer在Panic發(fā)生時執(zhí)行,第二個defer在Panic之后聲明,不能執(zhí)行到 func f3() { defer fmt.Println("panic前") panic("panic中") defer fmt.Println("panic后") }
defer,return,返回值的執(zhí)行順序
先來看3個例子
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 }
f1的執(zhí)行結果是6, f2的執(zhí)行結果是42,f3的執(zhí)行結果是6
在golang的官方文檔里面介紹了,return,defer,返回值的執(zhí)行順序:
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. 先給返回值賦值
2. 執(zhí)行defer語句
3. 包裹函數return返回
f1的結果是6。f1是匿名返回值,匿名返回值是在return執(zhí)行時被聲明,因此defer聲明時,還不能訪問到匿名返回值,defer的修改不會影響到返回值。
f2先給返回值r賦值,r=6,執(zhí)行defer語句,defer修改r, r = 42,然后函數return。
f3是有名返回值,但是因為r是作為defer的傳參,在聲明defer的時候,就進行參數拷貝傳遞,所以defer只會對defer函數的局部參數有影響,不會影響到調用函數的返回值。
閉包與匿名函數
匿名函數:沒有函數名的函數。
閉包:可以使用另外一個函數作用域中的變量的函數。
for i := 0; i <= 3; i++ { defer func() { fmt.Print(i) } } //輸出結果時 3,3,3,3 因為defer函數的i是對for循環(huán)i的引用,defer延遲執(zhí)行,for循環(huán)到最后i是3,到defer執(zhí)行時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源碼解析
defer的實現源碼是在runtime.deferproc
然后在函數返回之前的地方,運行函數runtime.deferreturn。
先了解defer結構體:
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 和 pc 分別指向了棧指針和調用方的程序計數器,fn是向 defer 關鍵字中傳入的函數,Panic是導致運行defer的Panic。
每遇到一個defer關鍵字,defer函數都會被轉換成runtime.deferproc
deferproc通過newdefer創(chuàng)建一個延遲函數,并將這個新建的延遲函數掛在當前goroutine的_defer的鏈表上
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會先從sched和當前p的deferpool取出一個_defer結構體,如果deferpool沒有_defer,則初始化一個新的_defer。
_defer是關聯到當前的g,所以defer只對當前g有效。
d.link = gp._defer
gp._defer = d //用鏈表連接當前g的所有defer
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 從當前g取出_defer鏈表執(zhí)行,每個_defer調用freedefer釋放_defer結構體,并將該_defer結構體放入當前p的deferpool中。
defer性能分析
defer在開發(fā)中,對于資源的釋放,捕獲Panic等很有用處??梢杂行╅_發(fā)者沒有考慮過defer對程序性能的影響,在程序中濫用defer。
在性能測試中可以發(fā)現,defer對性能還是有一些影響。雨痕的Go 性能優(yōu)化技巧 4/1,對defer語句帶來的額外開銷有一些測試。
測試代碼
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() }
測試結果:
BenchmarkNoDefer-4 100000000 11.1 ns/op BenchmarkDefer-4 36367237 33.1 ns/op
通過前面的源碼解析可以知道,defer會先調用deferproc,這些都會進行參數拷貝,deferreturn還會提取相關信息延遲執(zhí)行,這些都是比直接call一條語句消耗更大。
defer性能不高,每次defer耗時20ns,,在一個func內連續(xù)出現多次,性能消耗是20ns*n,累計出來浪費的cpu資源很大的。
解決之道:除了需要異常捕獲時,必須使用defer;其它資源回收類defer,可以判斷失敗后,使用goto跳轉到資源回收的代碼區(qū)。對于競爭資源,可以在使用完之后,立馬釋放資源,這樣才能最優(yōu)的使用競爭資源。