<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          一篇短文介紹 Defer 是如何工作的

          共 503字,需瀏覽 2分鐘

           ·

          2020-09-19 08:20

          點(diǎn)擊上方藍(lán)色“Go語(yǔ)言中文網(wǎng)”關(guān)注我們,領(lǐng)全套Go資料,每天學(xué)習(xí)?Go?語(yǔ)言

          Illustration created for “A Journey With Go”, made from the original Go Gopher, created by Renee French.

          ?? 這篇文章基于 Go 1.12。

          `defer` 語(yǔ)句[1]是在函數(shù)返回前執(zhí)行一段代碼的便捷方法,如 Golang 規(guī)范[2]所描述:

          延遲函數(shù)( deferred functions )在所在函數(shù)返回前,以與聲明相反的順序立即被調(diào)用

          以下是 LIFO (后進(jìn)先出)實(shí)現(xiàn)的例子:

          func?main()?{
          ???defer?func()?{
          ??????println(`defer?1`)
          ???}()
          ???defer?func()?{
          ??????println(`defer?2`)
          ???}()
          }
          defer?2?<-?后進(jìn)先出
          defer?1

          來(lái)看一下內(nèi)部的實(shí)現(xiàn),然后再看一個(gè)更復(fù)雜的案例。

          內(nèi)部實(shí)現(xiàn)

          Go 運(yùn)行時(shí)(runtime)使用一個(gè)鏈表來(lái)實(shí)現(xiàn) LIFO。實(shí)際上,一個(gè) defer 結(jié)構(gòu)體持有一個(gè)指向下一個(gè)要被執(zhí)行的 defer 結(jié)構(gòu)體的指針:

          type?_defer?struct?{
          ???siz?????int32
          ???started?bool
          ???sp??????uintptr
          ???pc??????uintptr
          ???fn??????*funcval
          ???_panic??*_panic
          ???link????*_defer?//?下一個(gè)要被執(zhí)行的延遲函數(shù)

          當(dāng)一個(gè)新的 defer 方法被創(chuàng)建的時(shí)候,它被附加到當(dāng)前的 Goroutine 上,然后之前的 defer 方法作為下一個(gè)要執(zhí)行的函數(shù)被鏈接到新創(chuàng)建的方法上:

          func?newdefer(siz?int32)?*_defer?{
          ???var?d?*_defer
          ???gp?:=?getg()?//?獲取當(dāng)前?goroutine
          ???[...]
          ???//?延遲列表現(xiàn)在被附加到新的?_defer?結(jié)構(gòu)體
          ???d.link?=?gp._defer
          ???gp._defer?=?d?//?新的結(jié)構(gòu)現(xiàn)在是第一個(gè)被調(diào)用的
          ???return?d
          }

          現(xiàn)在,后續(xù)調(diào)用會(huì)從棧的頂部依次出棧延遲函數(shù):

          func?deferreturn(arg0?uintptr)?{
          ???gp?:=?getg()?//?獲取當(dāng)前?goroutine
          ???d:=?gp._defer?//?拷貝延遲函數(shù)到一個(gè)變量上
          ???if?d?==?nil?{?//?如果不存在延遲函數(shù)就直接返回
          ??????return
          ???}
          ???[...]
          ???fn?:=?d.fn?//?獲取要調(diào)用的函數(shù)
          ???d.fn?=?nil?//?重置函數(shù)
          ???gp._defer?=?d.link?//?把下一個(gè)?_defer?結(jié)構(gòu)體依附到?Goroutine?上
          ???freedefer(d)?//?釋放?_defer?結(jié)構(gòu)體
          ???jmpdefer(fn,?uintptr(unsafe.Pointer(&arg0)))?//?調(diào)用該函數(shù)
          }

          如我們所見(jiàn),并沒(méi)有循環(huán)地去調(diào)用延遲函數(shù),而是一個(gè)接一個(gè)地出棧。這一行為可以通過(guò)生成匯編[3]代碼得到驗(yàn)證:

          // 第一個(gè)延遲函數(shù)
          0x001d 00029 (main.go:6) MOVL $0, (SP)
          0x0024 00036 (main.go:6) PCDATA $2, $1
          0x0024 00036 (main.go:6) LEAQ "".main.func1·f(SB), AX
          0x002b 00043 (main.go:6) PCDATA $2, $0
          0x002b 00043 (main.go:6) MOVQ AX, 8(SP)
          0x0030 00048 (main.go:6) CALL runtime.deferproc(SB)
          0x0035 00053 (main.go:6) TESTL AX, AX
          0x0037 00055 (main.go:6) JNE 117
          // 第二個(gè)延遲函數(shù)
          0x0039 00057 (main.go:10) MOVL $0, (SP)
          0x0040 00064 (main.go:10) PCDATA $2, $1
          0x0040 00064 (main.go:10) LEAQ "".main.func2·f(SB), AX
          0x0047 00071 (main.go:10) PCDATA $2, $0
          0x0047 00071 (main.go:10) MOVQ AX, 8(SP)
          0x004c 00076 (main.go:10) CALL runtime.deferproc(SB)
          0x0051 00081 (main.go:10) TESTL AX, AX
          0x0053 00083 (main.go:10) JNE 101
          // main 函數(shù)結(jié)束
          0x0055 00085 (main.go:18) XCHGL AX, AX
          0x0056 00086 (main.go:18) CALL runtime.deferreturn(SB)
          0x005b 00091 (main.go:18) MOVQ 16(SP), BP
          0x0060 00096 (main.go:18) ADDQ $24, SP
          0x0064 00100 (main.go:18) RET
          0x0065 00101 (main.go:10) XCHGL AX, AX
          0x0066 00102 (main.go:10) CALL runtime.deferreturn(SB)
          0x006b 00107 (main.go:10) MOVQ 16(SP), BP
          0x0070 00112 (main.go:10) ADDQ $24, SP
          0x0074 00116 (main.go:10) RET

          deferproc 方法被調(diào)用了兩次,并且內(nèi)部調(diào)用了 newdefer 方法,我們之前已經(jīng)看到該方法將我們的函數(shù)注冊(cè)為延遲函數(shù)。之后,在函數(shù)的最后,在 deferreturn 函數(shù)的幫助下,延遲方法會(huì)被一個(gè)接一個(gè)地調(diào)用。

          Go 標(biāo)準(zhǔn)庫(kù)向我們展示了結(jié)構(gòu)體 _defer 同樣鏈接了一個(gè) _panic *_panic 屬性。來(lái)通過(guò)另一個(gè)例子看下它在哪里會(huì)起作用。

          延遲和返回值

          如規(guī)范所描述,延遲函數(shù)訪問(wèn)返回的結(jié)果的唯一方法是使用命名返回參數(shù)[4]

          如果延遲函數(shù)是一個(gè)匿名函數(shù)( function literal )[5],并且所在函數(shù)存在命名返回參數(shù)[6],同時(shí)該命名返回參數(shù)在匿名函數(shù)的作用域中,匿名函數(shù)可能會(huì)在返回參數(shù)返回前訪問(wèn)并修改它們。

          這里有個(gè)例子:

          func?main()?{
          ???fmt.Printf("with?named?param,?x:?%d\n",?namedParam())
          ???fmt.Printf("without?named?param,?x:?%d\n",?notNamedParam())
          }
          func?namedParam()?(x?int)?{
          ???x?=?1
          ???defer?func()?{?x?=?2?}()
          ???return?x
          }

          func?notNamedParam()?(int)?{
          ???x?:=?1
          ???defer?func()?{?x?=?2?}()
          ???return?x
          }
          with?named?param,?x:?2
          without?named?param,?x:?1

          確實(shí)就像這篇“defer, panic 和 recover[7]”博客所描述的一樣,一旦確定這一行為,我們可以將其與 recover 函數(shù)混合使用:

          recover 函數(shù) 是一個(gè)用于重新獲取對(duì)恐慌(panicking)goroutine 控制的內(nèi)置函數(shù)。recover 函數(shù)僅在延遲函數(shù)內(nèi)部時(shí)才有效。

          如我們所見(jiàn),_defer 結(jié)構(gòu)體鏈接了一個(gè) _panic 屬性,該屬性在 panic 調(diào)用期間被鏈接。

          func?gopanic(e?interface{})?{
          ???[...]
          ???var?p?_panic
          ???[...]
          ???d?:=?gp._defer?//?當(dāng)前附加的?defer?函數(shù)
          ???[...]
          ???d._panic?=?(*_panic)(noescape(unsafe.Pointer(&p)))
          ???[...]
          }

          確實(shí),在發(fā)生 panic 的情況下,調(diào)用延遲函數(shù)之前會(huì)調(diào)用 gopanic 方法:

          0x0067 00103 (main.go:21)   CALL   runtime.gopanic(SB)
          0x006c 00108 (main.go:21) UNDEF
          0x006e 00110 (main.go:16) XCHGL AX, AX
          0x006f 00111 (main.go:16) CALL runtime.deferreturn(SB)

          這里是一個(gè) recover 函數(shù)利用命名返回參數(shù)的例子:

          func?main()?{
          ???fmt.Printf("error?from?err1:?%v\n",?err1())
          ???fmt.Printf("error?from?err2:?%v\n",?err2())
          }

          func?err1()?error?{
          ???var?err?error

          ???defer?func()?{
          ??????if?r?:=?recover();?r?!=?nil?{
          ?????????err?=?errors.New("recovered")
          ??????}
          ???}()
          ???panic(`foo`)

          ???return?err
          }

          func?err2()?(err?error)?{
          ???defer?func()?{
          ??????if?r?:=?recover();?r?!=?nil?{
          ?????????err?=?errors.New("recovered")
          ??????}
          ???}()
          ???panic(`foo`)

          ???return?err
          }
          error?from?err1:?<nil>
          error?from?err2:?recovered

          兩者的結(jié)合是我們可以正常使用 recover 函數(shù)將我們希望的 error 返回給調(diào)用方。作為這篇關(guān)于延遲函數(shù)的文章的總結(jié),讓我們來(lái)看看延遲函數(shù)的提升。

          性能提升

          Go 1.8[8]是提升 defer 的最近的一個(gè)版本(譯者注:目前 Go 1.14 才是提升 defer 性能的最近的一個(gè)版本),我們可以通過(guò)運(yùn)行 Go 的基準(zhǔn)測(cè)試來(lái)看到這些提升(在 1.7 和 1.8 之間進(jìn)行對(duì)比):

          name?????????old?time/op??new?time/op??delta
          Defer-4??????99.0ns?±?9%??52.4ns?±?5%??-47.04%??(p=0.000?n=9+10)
          Defer10-4????90.6ns?±?13%??45.0ns?±?3%??-50.37%??(p=0.000?n=10+10)

          這樣的提升得益于這個(gè)提升分配方式的 CL [9],避免了棧的增長(zhǎng)。

          不帶參數(shù)的 defer 語(yǔ)句避免內(nèi)存拷貝也是一個(gè)優(yōu)化。下面是帶參數(shù)和不帶參數(shù)的延遲函數(shù)的基準(zhǔn)測(cè)試:

          name?????old?time/op??new?time/op??delta
          Defer-4??51.3ns?±?3%??45.8ns?±?1%??-10.72%??(p=0.000?n=10+10)

          由于第二個(gè)優(yōu)化,現(xiàn)在速度也提高了 10%。


          via: https://medium.com/a-journey-with-go/go-how-does-defer-statement-work-1a9492689b6e

          作者:Vincent Blanchon[10]譯者:dust347[11]校對(duì):@unknwon[12]

          本文由 GCTT[13] 原創(chuàng)編譯,Go 中文網(wǎng)[14] 榮譽(yù)推出

          參考資料

          [1]

          defer 語(yǔ)句: https://golang.org/ref/spec#Defer_statements

          [2]

          Golang 規(guī)范: https://golang.org/ref/spec#Defer_statements

          [3]

          匯編: https://golang.org/doc/asm

          [4]

          命名返回參數(shù): https://golang.org/ref/spec#Function_types

          [5]

          匿名函數(shù)( function literal ): https://golang.org/ref/spec#Function_literals

          [6]

          命名返回參數(shù): https://golang.org/ref/spec#Function_types

          [7]

          defer, panic 和 recover: https://blog.golang.org/defer-panic-and-recover

          [8]

          Go 1.8: https://golang.org/doc/go1.8#defer

          [9]

          這個(gè)提升分配方式的 CL : https://go-review.googlesource.com/c/go/+/29656/

          [10]

          Vincent Blanchon: https://medium.com/@blanchon.vincent

          [11]

          dust347: https://github.com/dust347

          [12]

          @unknwon: https://github.com/unknwon

          [13]

          GCTT: https://github.com/studygolang/GCTT

          [14]

          Go 中文網(wǎng): https://studygolang.com/



          推薦閱讀


          學(xué)習(xí)交流 Go 語(yǔ)言,掃碼回復(fù)「進(jìn)群」即可


          站長(zhǎng) polarisxu

          自己的原創(chuàng)文章

          不限于 Go 技術(shù)

          職場(chǎng)和創(chuàng)業(yè)經(jīng)驗(yàn)


          Go語(yǔ)言中文網(wǎng)

          每天為你

          分享 Go 知識(shí)

          Go愛(ài)好者值得關(guān)注




          瀏覽 49
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  人人操人人爱人人射 | 日韩AAA级黄片 | 精品愛av| 欧美户外操逼 | 又黄又爽的美女裸体视频十八禁亚洲 |