溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

golang中defer的實(shí)現(xiàn)原理

發(fā)布時(shí)間:2021-09-09 15:36:46 來(lái)源:億速云 閱讀:146 作者:柒染 欄目:編程語(yǔ)言

golang中defer的實(shí)現(xiàn)原理,很多新手對(duì)此不是很清楚,為了幫助大家解決這個(gè)難題,下面小編將為大家詳細(xì)講解,有這方面需求的人可以來(lái)學(xué)習(xí)下,希望你能有所收獲。

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

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

defer的觸發(fā)時(shí)機(jī)

官網(wǎng)說(shuō)的很清楚:
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語(yǔ)句的函數(shù)返回時(shí)

  2. 包裹著defer語(yǔ)句的函數(shù)執(zhí)行到最后時(shí)

  3. 當(dāng)前goroutine發(fā)生Panic時(shí)

        //輸出結(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前  第一個(gè)defer在Panic發(fā)生時(shí)執(zhí)行,第二個(gè)defer在Panic之后聲明,不能執(zhí)行到
       func f3() {
           defer fmt.Println("panic前")
           panic("panic中")
           defer fmt.Println("panic后")
       }

defer,return,返回值的執(zhí)行順序

先來(lái)看3個(gè)例子

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語(yǔ)句
3. 包裹函數(shù)return返回

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

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

for i := 0; i <= 3; i++ {
    defer func() {
        fmt.Print(i)
    }
}
//輸出結(jié)果時(shí) 3,3,3,3
因?yàn)閐efer函數(shù)的i是對(duì)for循環(huán)i的引用,defer延遲執(zhí)行,for循環(huán)到最后i是3,到defer執(zhí)行時(shí)i就 
是3

for i := 0; i <= 3; i++ {
    defer func(i int) {
        fmt.Print(i)
    }(i)
}
//輸出結(jié)果時(shí) 3,2,1,0
因?yàn)閐efer函數(shù)的i是在defer聲明的時(shí)候,就當(dāng)作defer參數(shù)傳遞到defer函數(shù)中

defer源碼解析
defer的實(shí)現(xiàn)源碼是在runtime.deferproc
然后在函數(shù)返回之前的地方,運(yùn)行函數(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)用方的程序計(jì)數(shù)器,fn是向 defer 關(guān)鍵字中傳入的函數(shù),Panic是導(dǎo)致運(yùn)行defer的Panic。
每遇到一個(gè)defer關(guān)鍵字,defer函數(shù)都會(huì)被轉(zhuǎn)換成runtime.deferproc
deferproc通過(guò)newdefer創(chuàng)建一個(gè)延遲函數(shù),并將這個(gè)新建的延遲函數(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會(huì)先從sched和當(dāng)前p的deferpool取出一個(gè)_defer結(jié)構(gòu)體,如果deferpool沒(méi)有_defer,則初始化一個(gè)新的_defer。
_defer是關(guān)聯(lián)到當(dāng)前的g,所以defer只對(duì)當(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í)行,每個(gè)_defer調(diào)用freedefer釋放_(tái)defer結(jié)構(gòu)體,并將該_defer結(jié)構(gòu)體放入當(dāng)前p的deferpool中。

defer性能分析
defer在開(kāi)發(fā)中,對(duì)于資源的釋放,捕獲Panic等很有用處。可以有些開(kāi)發(fā)者沒(méi)有考慮過(guò)defer對(duì)程序性能的影響,在程序中濫用defer。
在性能測(cè)試中可以發(fā)現(xiàn),defer對(duì)性能還是有一些影響。雨痕的Go 性能優(yōu)化技巧 4/1,對(duì)defer語(yǔ)句帶來(lái)的額外開(kāi)銷(xiāo)有一些測(cè)試。

測(cè)試代碼

    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()
    }

測(cè)試結(jié)果:

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

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

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

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

看完上述內(nèi)容是否對(duì)您有幫助呢?如果還想對(duì)相關(guān)知識(shí)有進(jìn)一步的了解或閱讀更多相關(guān)文章,請(qǐng)關(guān)注億速云行業(yè)資訊頻道,感謝您對(duì)億速云的支持。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI