亚洲最大看欧美片,亚洲图揄拍自拍另类图片,欧美精品v国产精品v呦,日本在线精品视频免费

  • 站長資訊網(wǎng)
    最全最豐富的資訊網(wǎng)站

    一文詳解golang defer的實現(xiàn)原理

    本文由go語言教程欄目給大家介紹golang defer實現(xiàn)原理,希望對需要的朋友有所幫助!

    defer是golang提供的關(guān)鍵字,在函數(shù)或者方法執(zhí)行完成,返回之前調(diào)用。
    每次defer都會將defer函數(shù)壓入棧中,調(diào)用函數(shù)或者方法結(jié)束時,從棧中取出執(zhí)行,所以多個defer的執(zhí)行順序是先入后出。

    for i := 0; i <= 3; i++ {     defer fmt.Print(i) } //輸出結(jié)果時 3,2,1,0

    defer的觸發(fā)時機

    官網(wǎng)說的很清楚:
    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.

    1. 包裹著defer語句的函數(shù)返回時
    2. 包裹著defer語句的函數(shù)執(zhí)行到最后時
    3. 當(dāng)前goroutine發(fā)生Panic時

          //輸出結(jié)果:return前執(zhí)行defer    func f1() {        defer fmt.Println("return前執(zhí)行defer")        return     }     //輸出結(jié)果:函數(shù)執(zhí)行    // 函數(shù)執(zhí)行到最后    func f2() {        defer fmt.Println("函數(shù)執(zhí)行到最后")        fmt.Println("函數(shù)執(zhí)行")    }     //輸出結(jié)果: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í)行結(jié)果是6, f2的執(zhí)行結(jié)果是42,f3的執(zhí)行結(jié)果是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. 包裹函數(shù)return返回

    f1的結(jié)果是6。f1是匿名返回值,匿名返回值是在return執(zhí)行時被聲明,因此defer聲明時,還不能訪問到匿名返回值,defer的修改不會影響到返回值。
    f2先給返回值r賦值,r=6,執(zhí)行defer語句,defer修改r, r = 42,然后函數(shù)return。
    f3是有名返回值,但是因為r是作為defer的傳參,在聲明defer的時候,就進行參數(shù)拷貝傳遞,所以defer只會對defer函數(shù)的局部參數(shù)有影響,不會影響到調(diào)用函數(shù)的返回值。

    閉包與匿名函數(shù)
    匿名函數(shù):沒有函數(shù)名的函數(shù)。
    閉包:可以使用另外一個函數(shù)作用域中的變量的函數(shù)。

    for i := 0; i <= 3; i++ {     defer func() {         fmt.Print(i)     } } //輸出結(jié)果時 3,3,3,3 因為defer函數(shù)的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) } //輸出結(jié)果時 3,2,1,0 因為defer函數(shù)的i是在defer聲明的時候,就當(dāng)作defer參數(shù)傳遞到defer函數(shù)中

    defer源碼解析
    defer的實現(xiàn)源碼是在runtime.deferproc
    然后在函數(shù)返回之前的地方,運行函數(shù)runtime.deferreturn。
    先了解defer結(jié)構(gòu)體:

        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 分別指向了棧指針和調(diào)用方的程序計數(shù)器,fn是向 defer 關(guān)鍵字中傳入的函數(shù),Panic是導(dǎo)致運行defer的Panic。
    每遇到一個defer關(guān)鍵字,defer函數(shù)都會被轉(zhuǎn)換成runtime.deferproc
    deferproc通過newdefer創(chuàng)建一個延遲函數(shù),并將這個新建的延遲函數(shù)掛在當(dā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和當(dāng)前p的deferpool取出一個_defer結(jié)構(gòu)體,如果deferpool沒有_defer,則初始化一個新的_defer。
    _defer是關(guān)聯(lián)到當(dāng)前的g,所以defer只對當(dāng)前g有效。
    d.link = gp._defer
    gp._defer = d //用鏈表連接當(dāng)前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 從當(dāng)前g取出_defer鏈表執(zhí)行,每個_defer調(diào)用freedefer釋放_defer結(jié)構(gòu)體,并將該_defer結(jié)構(gòu)體放入當(dāng)前p的deferpool中。

    defer性能分析
    defer在開發(fā)中,對于資源的釋放,捕獲Panic等很有用處。可以有些開發(fā)者沒有考慮過defer對程序性能的影響,在程序中濫用defer。
    在性能測試中可以發(fā)現(xiàn),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()     }

    測試結(jié)果:

        BenchmarkNoDefer-4      100000000               11.1 ns/op     BenchmarkDefer-4        36367237                33.1 ns/op

    通過前面的源碼解析可以知道,defer會先調(diào)用deferproc,這些都會進行參數(shù)拷貝,deferreturn還會提取相關(guān)信息延遲執(zhí)行,這些都是比直接call一條語句消耗更大。

    defer性能不高,每次defer耗時20ns,,在一個func內(nèi)連續(xù)出現(xiàn)多次,性能消耗是20ns*n,累計出來浪費的cpu資源很大的。

    解決之道:除了需要異常捕獲時,必須使用defer;其它資源回收類defer,可以判斷失敗后,使用goto跳轉(zhuǎn)到資源回收的代碼區(qū)。對于競爭資源,可以在使用完之后,立馬釋放資源,這樣才能最優(yōu)的使用競爭資源。

    贊(0)
    分享到: 更多 (0)
    網(wǎng)站地圖   滬ICP備18035694號-2    滬公網(wǎng)安備31011702889846號