真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

Godefer的實(shí)現(xiàn)原理剖析

本篇內(nèi)容介紹了“Go defer的實(shí)現(xiàn)原理剖析”的有關(guān)知識,在實(shí)際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

成都創(chuàng)新互聯(lián)專業(yè)IDC數(shù)據(jù)服務(wù)器托管提供商,專業(yè)提供成都服務(wù)器托管,服務(wù)器租用,BGP機(jī)房服務(wù)器托管,BGP機(jī)房服務(wù)器托管,成都多線服務(wù)器托管等服務(wù)器托管服務(wù)。

1. 前言

defer語句用于延遲函數(shù)的調(diào)用,每次defer都會把一個(gè)函數(shù)壓入棧中,函數(shù)返回前再把延遲的函數(shù)取出并執(zhí)行。

為了方便描述,我們把創(chuàng)建defer的函數(shù)稱為主函數(shù),defer語句后面的函數(shù)稱為延遲函數(shù)。

延遲函數(shù)可能有輸入?yún)?shù),這些參數(shù)可能來源于定義defer的函數(shù),延遲函數(shù)也可能引用主函數(shù)用于返回的變量,也就是說延遲函數(shù)可能會影響主函數(shù)的一些行為,這些場景下,如果不了解defer的規(guī)則很容易出錯(cuò)。

其實(shí)官方說明的defer的三個(gè)原則很清楚,本節(jié)試圖匯總defer的使用場景并做簡單說明。

2. 熱身

按照慣例,我們看幾個(gè)有意思的題目,用于檢驗(yàn)對defer的了解程度。

2.1 題目一

下面函數(shù)輸出結(jié)果是什么?

func deferFuncParameter() {    var aInt = 1
    defer fmt.Println(aInt)
    aInt = 2
    return}

題目說明:
函數(shù)deferFuncParameter()定義一個(gè)整型變量并初始化為1,然后使用defer語句打印出變量值,最后修改變量值為2.

參考答案:
輸出1。延遲函數(shù)fmt.Println(aInt)的參數(shù)在defer語句出現(xiàn)時(shí)就已經(jīng)確定了,所以無論后面如何修改aInt變量都不會影響延遲函數(shù)。

2.2 題目二

下面程序輸出什么?

package mainimport "fmt"func printArray(array *[3]int) {    for i := range array {
        fmt.Println(array[i])
    }
}func deferFuncParameter() {
    var aArray = [3]int{1, 2, 3}    defer printArray(&aArray)
    aArray[0] = 10
    return}func main() {
    deferFuncParameter()
}

函數(shù)說明:
函數(shù)deferFuncParameter()定義一個(gè)數(shù)組,通過defer延遲函數(shù)printArray()的調(diào)用,最后修改數(shù)組第一個(gè)元素。printArray()函數(shù)接受數(shù)組的指針并把數(shù)組全部打印出來。

參考答案:
輸出10、2、3三個(gè)值。延遲函數(shù)printArray()的參數(shù)在defer語句出現(xiàn)時(shí)就已經(jīng)確定了,即數(shù)組的地址,由于延遲函數(shù)執(zhí)行時(shí)機(jī)是在return語句之前,所以對數(shù)組的最終修改值會被打印出來。

2.3 題目三

下面函數(shù)輸出什么?

func deferFuncReturn() (result int) {    i := 1
    defer func() {
       result++
    }()    return i}

函數(shù)說明:
函數(shù)擁有一個(gè)具名返回值result,函數(shù)內(nèi)部聲明一個(gè)變量i,defer指定一個(gè)延遲函數(shù),最后返回變量i。延遲函數(shù)中遞增result。

參考答案:
函數(shù)輸出2。函數(shù)的return語句并不是原子的,實(shí)際執(zhí)行分為設(shè)置返回值-->ret,defer語句實(shí)際執(zhí)行在返回前,即擁有defer的函數(shù)返回過程是:設(shè)置返回值-->執(zhí)行defer-->ret。所以return語句先把result設(shè)置為i的值,即1,defer語句中又把result遞增1,所以最終返回2。

3. defer規(guī)則

Golang官方博客里總結(jié)了defer的行為規(guī)則,只有三條,我們圍繞這三條進(jìn)行說明。

3.1 規(guī)則一:延遲函數(shù)的參數(shù)在defer語句出現(xiàn)時(shí)就已經(jīng)確定下來了

官方給出一個(gè)例子,如下所示:

func a() {    i := 0
    defer fmt.Println(i)
    i++
    return
}

defer語句中的fmt.Println()參數(shù)i值在defer出現(xiàn)時(shí)就已經(jīng)確定下來,實(shí)際上是拷貝了一份。后面對變量i的修改不會影響fmt.Println()函數(shù)的執(zhí)行,仍然打印"0"。

注意:對于指針類型參數(shù),規(guī)則仍然適用,只不過延遲函數(shù)的參數(shù)是一個(gè)地址值,這種情況下,defer后面的語句對變量的修改可能會影響延遲函數(shù)。

3.2 規(guī)則二:延遲函數(shù)執(zhí)行按后進(jìn)先出順序執(zhí)行,即先出現(xiàn)的defer最后執(zhí)行

這個(gè)規(guī)則很好理解,定義defer類似于入棧操作,執(zhí)行defer類似于出棧操作。

設(shè)計(jì)defer的初衷是簡化函數(shù)返回時(shí)資源清理的動作,資源往往有依賴順序,比如先申請A資源,再跟據(jù)A資源申請B資源,跟據(jù)B資源申請C資源,即申請順序是:A-->B-->C,釋放時(shí)往往又要反向進(jìn)行。這就是把deffer設(shè)計(jì)成FIFO的原因。

每申請到一個(gè)用完需要釋放的資源時(shí),立即定義一個(gè)defer來釋放資源是個(gè)很好的習(xí)慣。

3.3 規(guī)則三:延遲函數(shù)可能操作主函數(shù)的具名返回值

定義defer的函數(shù),即主函數(shù)可能有返回值,返回值有沒有名字沒有關(guān)系,defer所作用的函數(shù),即延遲函數(shù)可能會影響到返回值。

若要理解延遲函數(shù)是如何影響主函數(shù)返回值的,只要明白函數(shù)是如何返回的就足夠了。

3.3.1 函數(shù)返回過程

有一個(gè)事實(shí)必須要了解,關(guān)鍵字return不是一個(gè)原子操作,實(shí)際上return只代理匯編指令ret,即將跳轉(zhuǎn)程序執(zhí)行。比如語句return i,實(shí)際上分兩步進(jìn)行,即將i值存入棧中作為返回值,然后執(zhí)行跳轉(zhuǎn),而defer的執(zhí)行時(shí)機(jī)正是跳轉(zhuǎn)前,所以說defer執(zhí)行時(shí)還是有機(jī)會操作返回值的。

舉個(gè)實(shí)際的例子進(jìn)行說明這個(gè)過程:

func deferFuncReturn() (result int) {    i := 1
    defer func() {
       result++
    }()    return i}

該函數(shù)的return語句可以拆分成下面兩行:

result = i
return

而延遲函數(shù)的執(zhí)行正是在return之前,即加入defer后的執(zhí)行過程如下:

result = i
result++
return

所以上面函數(shù)實(shí)際返回i++值。

關(guān)于主函數(shù)有不同的返回方式,但返回機(jī)制就如上機(jī)介紹所說,只要把return語句拆開都可以很好的理解,下面分別舉例說明

3.3.1 主函數(shù)擁有匿名返回值,返回字面值

一個(gè)主函數(shù)擁有一個(gè)匿名的返回值,返回時(shí)使用字面值,比如返回"1"、"2"、"Hello"這樣的值,這種情況下defer語句是無法操作返回值的。

一個(gè)返回字面值的函數(shù),如下所示:

func foo() int {    var i int
    defer func() {
        i++
    }()    return 1}

上面的return語句,直接把1寫入棧中作為返回值,延遲函數(shù)無法操作該返回值,所以就無法影響返回值。

3.3.2 主函數(shù)擁有匿名返回值,返回變量

一個(gè)主函數(shù)擁有一個(gè)匿名的返回值,返回使用本地或全局變量,這種情況下defer語句可以引用到返回值,但不會改變返回值。

一個(gè)返回本地變量的函數(shù),如下所示:

func foo() int {    var i int
    defer func() {
        i++
    }()    return i
}

上面的函數(shù),返回一個(gè)局部變量,同時(shí)defer函數(shù)也會操作這個(gè)局部變量。對于匿名返回值來說,可以假定仍然有一個(gè)變量存儲返回值,假定返回值變量為"anony",上面的返回語句可以拆分成以下過程:

anony = i
i++
return

由于i是整型,會將值拷貝給anony,所以defer語句中修改i值,對函數(shù)返回值不造成影響。

3.3.3 主函數(shù)擁有具名返回值

主函聲明語句中帶名字的返回值,會被初始化成一個(gè)局部變量,函數(shù)內(nèi)部可以像使用局部變量一樣使用該返回值。如果defer語句操作該返回值,可能會改變返回結(jié)果。

一個(gè)影響函返回值的例子:

func foo() (ret int) {    defer func() {
        ret++
    }()    return 0}

上面的函數(shù)拆解出來,如下所示:

ret = 0
ret++
return

函數(shù)真正返回前,在defer中對返回值做了+1操作,所以函數(shù)最終返回1。

4. defer實(shí)現(xiàn)原理

本節(jié)我們嘗試了解一些defer的實(shí)現(xiàn)機(jī)制。

4.1 defer數(shù)據(jù)結(jié)構(gòu)

源碼包src/src/runtime/runtime2.go:_defer定義了defer的數(shù)據(jù)結(jié)構(gòu):

type _defer struct {
    sp      uintptr   //函數(shù)棧指針
    pc      uintptr   //程序計(jì)數(shù)器
    fn      *funcval  //函數(shù)地址
    link    *_defer   //指向自身結(jié)構(gòu)的指針,用于鏈接多個(gè)defer}

我們知道defer后面一定要接一個(gè)函數(shù)的,所以defer的數(shù)據(jù)結(jié)構(gòu)跟一般函數(shù)類似,也有棧地址、程序計(jì)數(shù)器、函數(shù)地址等等。

與函數(shù)不同的一點(diǎn)是它含有一個(gè)指針,可用于指向另一個(gè)defer,每個(gè)goroutine數(shù)據(jù)結(jié)構(gòu)中實(shí)際上也有一個(gè)defer指針,該指針指向一個(gè)defer的單鏈表,每次聲明一個(gè)defer時(shí)就將defer插入到單鏈表表頭,每次執(zhí)行defer時(shí)就從單鏈表表頭取出一個(gè)defer執(zhí)行。

下圖展示一個(gè)goroutine定義多個(gè)defer時(shí)的場景: Go defer的實(shí)現(xiàn)原理剖析

從上圖可以看到,新聲明的defer總是添加到鏈表頭部。

函數(shù)返回前執(zhí)行defer則是從鏈表首部依次取出執(zhí)行,不再贅述。

一個(gè)goroutine可能連續(xù)調(diào)用多個(gè)函數(shù),defer添加過程跟上述流程一致,進(jìn)入函數(shù)時(shí)添加defer,離開函數(shù)時(shí)取出defer,所以即便調(diào)用多個(gè)函數(shù),也總是能保證defer是按FIFO方式執(zhí)行的。

4.2 defer的創(chuàng)建和執(zhí)行

源碼包src/runtime/panic.go定義了兩個(gè)方法分別用于創(chuàng)建defer和執(zhí)行defer。

  • deferproc(): 在聲明defer處調(diào)用,其將defer函數(shù)存入goroutine的鏈表中;

  • deferreturn():在return指令,準(zhǔn)確的講是在ret指令前調(diào)用,其將defer從goroutine鏈表中取出并執(zhí)行。

可以簡單這么理解,在編譯在階段,聲明defer處插入了函數(shù)deferproc(),在函數(shù)return前插入了函數(shù)deferreturn()。

5. 總結(jié)

  • defer定義的延遲函數(shù)參數(shù)在defer語句出時(shí)就已經(jīng)確定下來了

  • defer定義順序與實(shí)際執(zhí)行順序相反

  • return不是原子操作,執(zhí)行過程是: 保存返回值(若有)-->執(zhí)行defer(若有)-->執(zhí)行ret跳轉(zhuǎn)

  • 申請資源后立即使用defer關(guān)閉資源是好習(xí)慣

“Go defer的實(shí)現(xiàn)原理剖析”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!


標(biāo)題名稱:Godefer的實(shí)現(xiàn)原理剖析
本文URL:http://weahome.cn/article/jhipsd.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部