您好,登錄后才能下訂單哦!
golang中defer的實現原理,很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細講解,有這方面需求的人可以來學習下,希望你能有所收獲。
defer是golang提供的關鍵字,在函數或者方法執行完成,返回之前調用。
每次defer都會將defer函數壓入棧中,調用函數或者方法結束時,從棧中取出執行,所以多個defer的執行順序是先入后出。
for i := 0; i <= 3; i++ { defer fmt.Print(i) } //輸出結果時 3,2,1,0
defer的觸發時機
官網說的很清楚:
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語句的函數執行到最后時
當前goroutine發生Panic時
//輸出結果: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后") }
defer,return,返回值的執行順序
先來看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的執行結果是6, f2的執行結果是42,f3的執行結果是6
在golang的官方文檔里面介紹了,return,defer,返回值的執行順序:
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. 執行defer語句
3. 包裹函數return返回
f1的結果是6。f1是匿名返回值,匿名返回值是在return執行時被聲明,因此defer聲明時,還不能訪問到匿名返回值,defer的修改不會影響到返回值。
f2先給返回值r賦值,r=6,執行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循環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源碼解析
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創建一個延遲函數,并將這個新建的延遲函數掛在當前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鏈表執行,每個_defer調用freedefer釋放_defer結構體,并將該_defer結構體放入當前p的deferpool中。
defer性能分析
defer在開發中,對于資源的釋放,捕獲Panic等很有用處。可以有些開發者沒有考慮過defer對程序性能的影響,在程序中濫用defer。
在性能測試中可以發現,defer對性能還是有一些影響。雨痕的Go 性能優化技巧 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還會提取相關信息延遲執行,這些都是比直接call一條語句消耗更大。
defer性能不高,每次defer耗時20ns,,在一個func內連續出現多次,性能消耗是20ns*n,累計出來浪費的cpu資源很大的。
解決之道:除了需要異常捕獲時,必須使用defer;其它資源回收類defer,可以判斷失敗后,使用goto跳轉到資源回收的代碼區。對于競爭資源,可以在使用完之后,立馬釋放資源,這樣才能最優的使用競爭資源。
看完上述內容是否對您有幫助呢?如果還想對相關知識有進一步的了解或閱讀更多相關文章,請關注億速云行業資訊頻道,感謝您對億速云的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。