成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

深入理解 Go defer

Developer / 1153人閱讀

摘要:在上一章節(jié)深入理解中,我們發(fā)現(xiàn)了與其關(guān)聯(lián)性極大,還是覺(jué)得非常有必要深入一下。而返回的就是,因此可以防止重復(fù)調(diào)用小結(jié)在這個(gè)函數(shù)中會(huì)為新的設(shè)置一些基礎(chǔ)屬性,并將調(diào)用函數(shù)的參數(shù)集傳入。

在上一章節(jié) 《深入理解 Go panic and recover》 中,我們發(fā)現(xiàn)了 defer 與其關(guān)聯(lián)性極大,還是覺(jué)得非常有必要深入一下。希望通過(guò)本章節(jié)大家可以對(duì) defer 關(guān)鍵字有一個(gè)深刻的理解,那么我們開始吧。你先等等,請(qǐng)排好隊(duì),我們這兒采取后進(jìn)先出 LIFO 的出站方式...

原文地址:深入理解 Go defer

特性

我們簡(jiǎn)單的過(guò)一下 defer 關(guān)鍵字的基礎(chǔ)使用,讓大家先有一個(gè)基礎(chǔ)的認(rèn)知

一、延遲調(diào)用
func main() {
    defer log.Println("EDDYCJY.")

    log.Println("end.")
}

輸出結(jié)果:

$ go run main.go            
2019/05/19 21:15:02 end.
2019/05/19 21:15:02 EDDYCJY.
二、后進(jìn)先出
func main() {
    for i := 0; i < 6; i++ {
        defer log.Println("EDDYCJY" + strconv.Itoa(i) + ".")
    }


    log.Println("end.")
}

輸出結(jié)果:

$ go run main.go
2019/05/19 21:19:17 end.
2019/05/19 21:19:17 EDDYCJY5.
2019/05/19 21:19:17 EDDYCJY4.
2019/05/19 21:19:17 EDDYCJY3.
2019/05/19 21:19:17 EDDYCJY2.
2019/05/19 21:19:17 EDDYCJY1.
2019/05/19 21:19:17 EDDYCJY0.
三、運(yùn)行時(shí)間點(diǎn)
func main() {
    func() {
         defer log.Println("defer.EDDYCJY.")
    }()

    log.Println("main.EDDYCJY.")
}

輸出結(jié)果:

$ go run main.go 
2019/05/22 23:30:27 defer.EDDYCJY.
2019/05/22 23:30:27 main.EDDYCJY.
四、異常處理
func main() {
    defer func() {
        if e := recover(); e != nil {
            log.Println("EDDYCJY.")
        }
    }()

    panic("end.")
}

輸出結(jié)果:

$ go run main.go 
2019/05/20 22:22:57 EDDYCJY.
源碼剖析
$ go tool compile -S main.go 
"".main STEXT size=163 args=0x0 locals=0x40
    ...
    0x0059 00089 (main.go:6)    MOVQ    AX, 16(SP)
    0x005e 00094 (main.go:6)    MOVQ    $1, 24(SP)
    0x0067 00103 (main.go:6)    MOVQ    $1, 32(SP)
    0x0070 00112 (main.go:6)    CALL    runtime.deferproc(SB)
    0x0075 00117 (main.go:6)    TESTL    AX, AX
    0x0077 00119 (main.go:6)    JNE    137
    0x0079 00121 (main.go:7)    XCHGL    AX, AX
    0x007a 00122 (main.go:7)    CALL    runtime.deferreturn(SB)
    0x007f 00127 (main.go:7)    MOVQ    56(SP), BP
    0x0084 00132 (main.go:7)    ADDQ    $64, SP
    0x0088 00136 (main.go:7)    RET
    0x0089 00137 (main.go:6)    XCHGL    AX, AX
    0x008a 00138 (main.go:6)    CALL    runtime.deferreturn(SB)
    0x008f 00143 (main.go:6)    MOVQ    56(SP), BP
    0x0094 00148 (main.go:6)    ADDQ    $64, SP
    0x0098 00152 (main.go:6)    RET
    ...

首先我們需要找到它,找到它實(shí)際對(duì)應(yīng)什么執(zhí)行代碼。通過(guò)匯編代碼,可得知涉及如下方法:

runtime.deferproc

runtime.deferreturn

很顯然是運(yùn)行時(shí)的方法,是對(duì)的人。我們繼續(xù)往下走看看都分別承擔(dān)了什么行為

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

在開始前我們需要先介紹一下 defer 的基礎(chǔ)單元 _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
}

...
type funcval struct {
    fn uintptr
    // variable-size, fn-specific data here
}

siz:所有傳入?yún)?shù)的總大小

started:該 defer 是否已經(jīng)執(zhí)行過(guò)

sp:函數(shù)棧指針寄存器,一般指向當(dāng)前函數(shù)棧的棧頂

pc:程序計(jì)數(shù)器,有時(shí)稱為指令指針(IP),線程利用它來(lái)跟蹤下一個(gè)要執(zhí)行的指令。在大多數(shù)處理器中,PC指向的是下一條指令,而不是當(dāng)前指令

fn:指向傳入的函數(shù)地址和參數(shù)

_panic:指向 _panic 鏈表

link:指向 _defer 鏈表

deferproc
func deferproc(siz int32, fn *funcval) {
    ...
    sp := getcallersp()
    argp := uintptr(unsafe.Pointer(&fn)) + unsafe.Sizeof(fn)
    callerpc := getcallerpc()

    d := newdefer(siz)
    ...
    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()
}

獲取調(diào)用 defer 函數(shù)的函數(shù)棧指針、傳入函數(shù)的參數(shù)具體地址以及PC (程序計(jì)數(shù)器),也就是下一個(gè)要執(zhí)行的指令。這些相當(dāng)于是預(yù)備參數(shù),便于后續(xù)的流轉(zhuǎn)控制

創(chuàng)建一個(gè)新的 defer 最小單元 _defer,填入先前準(zhǔn)備的參數(shù)

調(diào)用 memmove 將傳入的參數(shù)存儲(chǔ)到新 _defer (當(dāng)前使用)中去,便于后續(xù)的使用

最后調(diào)用 return0 進(jìn)行返回,這個(gè)函數(shù)非常重要。能夠避免在 deferproc 中又因?yàn)榉祷?return,而誘發(fā) deferreturn 方法的調(diào)用。其根本原因是一個(gè)停止 panic 的延遲方法會(huì)使 deferproc 返回 1,但在機(jī)制中如果 deferproc 返回不等于 0,將會(huì)總是檢查返回值并跳轉(zhuǎn)到函數(shù)的末尾。而 return0 返回的就是 0,因此可以防止重復(fù)調(diào)用

小結(jié)

這個(gè)函數(shù)中會(huì)為新的 _defer 設(shè)置一些基礎(chǔ)屬性,并將調(diào)用函數(shù)的參數(shù)集傳入。最后通過(guò)特殊的返回方法結(jié)束函數(shù)調(diào)用。另外這一塊與先前 《深入理解 Go panic and recover》 的處理邏輯有一定關(guān)聯(lián)性,其實(shí)就是 gp.sched.ret 返回 0 還是 1 會(huì)分流至不同處理方式

newdefer
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 {
            ...
            lock(&sched.deferlock)
            d := sched.deferpool[sc]
            unlock(&sched.deferlock)
        }
        ...
    }
    if d == nil {
        systemstack(func() {
            total := roundupsize(totaldefersize(uintptr(siz)))
            d = (*_defer)(mallocgc(total, deferType, true))
        })
        ...
    }
    d.siz = siz
    d.link = gp._defer
    gp._defer = d
    return d
}

從池中獲取可以使用的 _defer,則復(fù)用作為新的基礎(chǔ)單元

若在池中沒(méi)有獲取到可用的,則調(diào)用 mallocgc 重新申請(qǐng)一個(gè)新的

設(shè)置 defer 的基礎(chǔ)屬性,最后修改當(dāng)前 Goroutine_defer 指向

通過(guò)這個(gè)方法我們可以注意到兩點(diǎn),如下:

deferGoroutine(g) 有直接關(guān)系,所以討論 defer 時(shí)基本離不開 g 的關(guān)聯(lián)

新的 defer 總是會(huì)在現(xiàn)有的鏈表中的最前面,也就是 defer 的特性后進(jìn)先出

小結(jié)

這個(gè)函數(shù)主要承擔(dān)了獲取新的 _defer 的作用,它有可能是從 deferpool 中獲取的,也有可能是重新申請(qǐng)的

deferreturn
func deferreturn(arg0 uintptr) {
    gp := getg()
    d := gp._defer
    if d == nil {
        return
    }
    sp := getcallersp()
    if d.sp != sp {
        return
    }

    switch d.siz {
    case 0:
        // Do nothing.
    case sys.PtrSize:
        *(*uintptr)(unsafe.Pointer(&arg0)) = *(*uintptr)(deferArgs(d))
    default:
        memmove(unsafe.Pointer(&arg0), deferArgs(d), uintptr(d.siz))
    }
    fn := d.fn
    d.fn = nil
    gp._defer = d.link
    freedefer(d)
    jmpdefer(fn, uintptr(unsafe.Pointer(&arg0)))
}

如果在一個(gè)方法中調(diào)用過(guò) defer 關(guān)鍵字,那么編譯器將會(huì)在結(jié)尾處插入 deferreturn 方法的調(diào)用。而該方法中主要做了如下事項(xiàng):

清空當(dāng)前節(jié)點(diǎn) _defer 被調(diào)用的函數(shù)調(diào)用信息

釋放當(dāng)前節(jié)點(diǎn)的 _defer 的存儲(chǔ)信息并放回池中(便于復(fù)用)

跳轉(zhuǎn)到調(diào)用 defer 關(guān)鍵字的調(diào)用函數(shù)處

在這段代碼中,跳轉(zhuǎn)方法 jmpdefer 格外重要。因?yàn)樗@式的控制了流轉(zhuǎn),代碼如下:

// asm_amd64.s
TEXT runtime·jmpdefer(SB), NOSPLIT, $0-16
    MOVQ    fv+0(FP), DX    // fn
    MOVQ    argp+8(FP), BX    // caller sp
    LEAQ    -8(BX), SP    // caller sp after CALL
    MOVQ    -8(SP), BP    // restore BP as if deferreturn returned (harmless if framepointers not in use)
    SUBQ    $5, (SP)    // return to CALL again
    MOVQ    0(DX), BX
    JMP    BX    // but first run the deferred function

通過(guò)源碼的分析,我們發(fā)現(xiàn)它做了兩個(gè)很 “奇怪” 又很重要的事,如下:

MOVQ -8(SP), BP:-8(BX) 這個(gè)位置保存的是 deferreturn 執(zhí)行完畢后的地址

SUBQ $5, (SP):SP 的地址減 5 ,其減掉的長(zhǎng)度就恰好是 runtime.deferreturn 的長(zhǎng)度

你可能會(huì)問(wèn),為什么是 5?好吧。翻了半天最后看了一下匯編代碼...嗯,相減的確是 5 沒(méi)毛病,如下:

    0x007a 00122 (main.go:7)    CALL    runtime.deferreturn(SB)
    0x007f 00127 (main.go:7)    MOVQ    56(SP), BP

我們整理一下思緒,照上述邏輯的話,那 deferreturn 就是一個(gè) “遞歸” 了哦。每次都會(huì)重新回到 deferreturn 函數(shù),那它在什么時(shí)候才會(huì)結(jié)束呢,如下:

func deferreturn(arg0 uintptr) {
    gp := getg()
    d := gp._defer
    if d == nil {
        return
    }
    ...
}

也就是會(huì)不斷地進(jìn)入 deferreturn 函數(shù),判斷鏈表中是否還存著 _defer。若已經(jīng)不存在了,則返回,結(jié)束掉它。簡(jiǎn)單來(lái)講,就是處理完全部 defer 才允許你真的離開它。果真如此嗎?我們?cè)倏纯瓷厦娴膮R編代碼,如下:

    。..
    0x0070 00112 (main.go:6)    CALL    runtime.deferproc(SB)
    0x0075 00117 (main.go:6)    TESTL    AX, AX
    0x0077 00119 (main.go:6)    JNE    137
    0x0079 00121 (main.go:7)    XCHGL    AX, AX
    0x007a 00122 (main.go:7)    CALL    runtime.deferreturn(SB)
    0x007f 00127 (main.go:7)    MOVQ    56(SP), BP
    0x0084 00132 (main.go:7)    ADDQ    $64, SP
    0x0088 00136 (main.go:7)    RET
    0x0089 00137 (main.go:6)    XCHGL    AX, AX
    0x008a 00138 (main.go:6)    CALL    runtime.deferreturn(SB)
    ...

的確如上述流程所分析一致,驗(yàn)證完畢

小結(jié)

這個(gè)函數(shù)主要承擔(dān)了清空已使用的 defer 和跳轉(zhuǎn)到調(diào)用 defer 關(guān)鍵字的函數(shù)處,非常重要

總結(jié)

我們有提到 defer 關(guān)鍵字涉及兩個(gè)核心的函數(shù),分別是 deferprocdeferreturn 函數(shù)。而 deferreturn 函數(shù)比較特殊,是當(dāng)應(yīng)用函數(shù)調(diào)用 defer 關(guān)鍵字時(shí),編譯器會(huì)在其結(jié)尾處插入 deferreturn 的調(diào)用,它們倆一般都是成對(duì)出現(xiàn)的

但是當(dāng)一個(gè) Goroutine 上存在著多次 defer 行為(也就是多個(gè) _defer)時(shí),編譯器會(huì)進(jìn)行利用一些小技巧, 重新回到 deferreturn 函數(shù)去消耗 _defer 鏈表,直到一個(gè)不剩才允許真正的結(jié)束

而新增的基礎(chǔ)單元 _defer,有可能是被復(fù)用的,也有可能是全新申請(qǐng)的。它最后都會(huì)被追加到 _defer 鏈表的表頭,從而設(shè)定了后進(jìn)先出的調(diào)用特性

關(guān)聯(lián)

深入理解 Go panic and recover

參考

Scheduling In Go

Dive into stack and defer/panic/recover in go

golang-notes

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/31599.html

相關(guān)文章

  • 深入理解 Go panic and recover

    摘要:恢復(fù)流程如下判斷當(dāng)前中的是否已標(biāo)注為處理從鏈表中刪除已標(biāo)注中止的事件,也就是刪除已經(jīng)被恢復(fù)的事件將相關(guān)需要恢復(fù)的棧幀信息傳遞給方法的參數(shù)每個(gè)棧幀對(duì)應(yīng)著一個(gè)未運(yùn)行完的函數(shù)。 作為一個(gè) gophper,我相信你對(duì)于 panic 和 recover 肯定不陌生,但是你有沒(méi)有想過(guò)。當(dāng)我們執(zhí)行了這兩條語(yǔ)句之后。底層到底發(fā)生了什么事呢?前幾天和同事剛好聊到相關(guān)的話題,發(fā)現(xiàn)其實(shí)大家對(duì)這塊理解還是比較...

    banana_pi 評(píng)論0 收藏0
  • Go defer 會(huì)有性能損耗,盡量不要用?

    摘要:原文地址會(huì)有性能損耗,盡量不要用上個(gè)月在軒脈刃的全棧技術(shù)群里看到一個(gè)小伙伴問(wèn)說(shuō)在棧退出時(shí)執(zhí)行,會(huì)有性能損耗,盡量不要用,這個(gè)怎么解。因此,對(duì)于會(huì)有性能損耗,盡量不能用這個(gè)問(wèn)題,我認(rèn)為該用就用,應(yīng)該及時(shí)關(guān)閉就不要延遲,在用時(shí)一定要想清楚場(chǎng)景。 showImg(https://i.imgur.com/YlKjnSH.jpg); 原文地址:Go defer 會(huì)有性能損耗,盡量不要用? 上個(gè)月...

    wangshijun 評(píng)論0 收藏0
  • PHP 協(xié)程:Go + Chan + Defer

    摘要:為語(yǔ)言提供了強(qiáng)大的協(xié)程編程模式。提供的協(xié)程語(yǔ)法借鑒自,在此向開發(fā)組致敬協(xié)程可以與很好地互補(bǔ)。并發(fā)執(zhí)行使用創(chuàng)建協(xié)程,可以讓和兩個(gè)函數(shù)變成并發(fā)執(zhí)行。協(xié)程需要拿到請(qǐng)求的結(jié)果。 Swoole4為PHP語(yǔ)言提供了強(qiáng)大的CSP協(xié)程編程模式。底層提供了3個(gè)關(guān)鍵詞,可以方便地實(shí)現(xiàn)各類功能。 Swoole4提供的PHP協(xié)程語(yǔ)法借鑒自Golang,在此向GO開發(fā)組致敬 PHP+Swoole協(xié)程可以與...

    nidaye 評(píng)論0 收藏0
  • Go語(yǔ)言的變量、函數(shù)、Socks5代理服務(wù)器

    摘要:還有一種情況就是當(dāng)你在一行中寫了多個(gè)語(yǔ)句,也需要使用分號(hào)來(lái)分開由于語(yǔ)言詞法分析器添加分號(hào)的特殊性,所以在有些情況下需要注意你都不應(yīng)該將一個(gè)控制結(jié)構(gòu)或的左大括號(hào)放在下一行。 Go語(yǔ)言中變量的聲明和JavaScript很像,使用var關(guān)鍵字,變量的聲明、定義有好幾種形式 1. 變量和常量 // 聲明并初始化一個(gè)變量 var m int = 10 // 聲明初始化多個(gè)變量 var i, j...

    simon_chen 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<