go語言defer詳解 go語言 func

go語言中關(guān)于defer的注意事項(xiàng)

舉個(gè)例子,如下

專注于為中小企業(yè)提供網(wǎng)站設(shè)計(jì)制作、成都網(wǎng)站設(shè)計(jì)服務(wù),電腦端+手機(jī)端+微信端的三站合一,更高效的管理,為中小企業(yè)新和免費(fèi)做網(wǎng)站提供優(yōu)質(zhì)的服務(wù)。我們立足成都,凝聚了一批互聯(lián)網(wǎng)行業(yè)人才,有力地推動(dòng)了1000多家企業(yè)的穩(wěn)健成長(zhǎng),幫助中小企業(yè)通過網(wǎng)站建設(shè)實(shí)現(xiàn)規(guī)模擴(kuò)充和轉(zhuǎn)變。

答案

解析:

defer函數(shù)在處理Panic() 和Recover()時(shí)的應(yīng)用

panic 函數(shù)是內(nèi)置的go函數(shù),它 終止 go程序的當(dāng)前流程并開始 panicking , recover 函數(shù)也是內(nèi)置的一個(gè)go函數(shù),允許你收回處理那些使用了 panic 函數(shù)的 goroutine 的控制權(quán)

來個(gè)案例

1.14版本defer性能大幅度提升,內(nèi)部實(shí)現(xiàn)了開放編碼優(yōu)化

GO中的defer會(huì)在當(dāng)前函數(shù)返回前執(zhí)行傳入的函數(shù),常用于關(guān)閉文件描述符,關(guān)閉鏈接及解鎖等操作。

Go語言中使用defer時(shí)會(huì)遇到兩個(gè)常見問題:

接下來我們來詳細(xì)處理這兩個(gè)問題。

官方有段對(duì)defer的解釋:

這里我們先來一道經(jīng)典的面試題

你覺得這個(gè)會(huì)打印什么?

輸出結(jié)果:

這里是遵循先入后出的原則,同時(shí)保留當(dāng)前變量的值。

把這道題簡(jiǎn)化一下:

輸出結(jié)果

上述代碼輸出似乎不符合預(yù)期,這個(gè)現(xiàn)象出現(xiàn)的原因是什么呢?經(jīng)過分析,我們發(fā)現(xiàn)調(diào)用defer關(guān)鍵字會(huì)立即拷貝函數(shù)中引用的外部參數(shù),所以fmt.Println(i)的這個(gè)i是在調(diào)用defer的時(shí)候就已經(jīng)賦值了,所以會(huì)直接打印1。

想要解決這個(gè)問題也很簡(jiǎn)單,只需要向defer關(guān)鍵字傳入匿名函數(shù)

這里把一些垃圾回收使用的字段忽略了。

中間代碼生成階段cmd/compile/internal/gc/ssa.go會(huì)處理程序中的defer,該函數(shù)會(huì)根據(jù)條件不同,使用三種機(jī)制來處理該關(guān)鍵字

開放編碼、堆分配和棧分配是defer關(guān)鍵字的三種方法,而Go1.14加入的開放編碼,使得關(guān)鍵字開銷可以忽略不計(jì)。

call方法會(huì)為所有函數(shù)和方法調(diào)用生成中間代碼,工作內(nèi)容:

defer關(guān)鍵字在運(yùn)行時(shí)會(huì)調(diào)用deferproc,這個(gè)函數(shù)實(shí)現(xiàn)在src/runtime/panic.go里,接受兩個(gè)參數(shù):參數(shù)的大小和閉包所在的地址。

編譯器不僅將defer關(guān)鍵字轉(zhuǎn)成deferproc函數(shù),還會(huì)通過以下三種方式為所有調(diào)用defer的函數(shù)末尾插入deferreturn的函數(shù)調(diào)用

1、在cmd/compile/internal/gc/walk.go的walkstmt函數(shù)中,在遇到ODEFFER節(jié)點(diǎn)時(shí)會(huì)執(zhí)行Curfn.Func.SetHasDefer(true),設(shè)置當(dāng)前函數(shù)的hasdefer屬性

2、在ssa.go的buildssa會(huì)執(zhí)行s.hasdefer = fn.Func.HasDefer()更新hasdefer

3、在exit中會(huì)根據(jù)hasdefer在函數(shù)返回前插入deferreturn的函數(shù)調(diào)用

runtime.deferproc為defer創(chuàng)建了一個(gè)runtime._defer結(jié)構(gòu)體、設(shè)置它的函數(shù)指針fn、程序計(jì)數(shù)器pc和棧指針sp并將相關(guān)參數(shù)拷貝到相鄰的內(nèi)存空間中

最后調(diào)用的return0是唯一一個(gè)不會(huì)觸發(fā)延遲調(diào)用的函數(shù),可以避免deferreturn的遞歸調(diào)用。

newdefer的分配方式是從pool緩存池中獲?。?/p>

這三種方式取到的結(jié)構(gòu)體_defer,都會(huì)被添加到鏈表的隊(duì)頭,這也是為什么defer按照后進(jìn)先出的順序執(zhí)行。

deferreturn就是從鏈表的隊(duì)頭取出并調(diào)用jmpdefer傳入需要執(zhí)行的函數(shù)和參數(shù)。

該函數(shù)只有在所有延遲函數(shù)都執(zhí)行后才會(huì)返回。

如果我們能夠?qū)⒉糠纸Y(jié)構(gòu)體分配到棧上就可以節(jié)約內(nèi)存分配帶來的額外開銷。

在call函數(shù)中有在棧上分配

在運(yùn)行期間deferprocStack只需要設(shè)置一些未在編譯期間初始化的字段,就可以將棧上的_defer追加到函數(shù)的鏈表上。

除了分配的位置和堆的不同,其他的大致相同。

Go語言在1.14中通過開放編碼實(shí)現(xiàn)defer關(guān)鍵字,使用代碼內(nèi)聯(lián)優(yōu)化defer關(guān)鍵的額外開銷并引入函數(shù)數(shù)據(jù)funcdata管理panic的調(diào)用,該優(yōu)化可以將 defer 的調(diào)用開銷從 1.13 版本的 ~35ns 降低至 ~6ns 左右。

然而開放編碼作為一種優(yōu)化 defer 關(guān)鍵字的方法,它不是在所有的場(chǎng)景下都會(huì)開啟的,開放編碼只會(huì)在滿足以下的條件時(shí)啟用:

如果函數(shù)中defer關(guān)鍵字的數(shù)量多于8個(gè)或者defer處于循環(huán)中,那么就會(huì)禁用開放編碼優(yōu)化。

可以看到這里,判斷編譯參數(shù)不用-N,返回語句的數(shù)量和defer數(shù)量的乘積小于15,會(huì)啟用開放編碼優(yōu)化。

延遲比特deferBitsTemp和延遲記錄是使用開放編碼實(shí)現(xiàn)defer的兩個(gè)最重要的結(jié)構(gòu),一旦使用開放編碼,buildssa會(huì)在棧上初始化大小為8個(gè)比特的deferBits

延遲比特中的每一個(gè)比特位都表示該位對(duì)應(yīng)的defer關(guān)鍵字是否需要被執(zhí)行。延遲比特的作用就是標(biāo)記哪些defer關(guān)鍵字在函數(shù)中被執(zhí)行,這樣就能在函數(shù)返回時(shí)根據(jù)對(duì)應(yīng)的deferBits確定要執(zhí)行的函數(shù)。

而deferBits的大小為8比特,所以該優(yōu)化的條件就是defer的數(shù)量小于8.

而執(zhí)行延遲調(diào)用的時(shí)候仍在deferreturn

這里做了特殊的優(yōu)化,在runOpenDeferFrame執(zhí)行開放編碼延遲函數(shù)

1、從結(jié)構(gòu)體_defer讀取deferBits,執(zhí)行函數(shù)等信息

2、在循環(huán)中依次讀取執(zhí)行函數(shù)的地址和參數(shù)信息,并通過deferBits判斷是否要執(zhí)行

3、調(diào)用reflectcallSave執(zhí)行函數(shù)

1、新加入的defer放入隊(duì)頭,執(zhí)行defer時(shí)是從隊(duì)頭取函數(shù)調(diào)用,所以是后進(jìn)先出

2、通過判斷defer關(guān)鍵字、return數(shù)量來判斷是否開啟開放編碼優(yōu)化

3、調(diào)用deferproc函數(shù)創(chuàng)建新的延遲調(diào)用函數(shù)時(shí),會(huì)立即拷貝函數(shù)的參數(shù),函數(shù)的參數(shù)不會(huì)等到真正執(zhí)行時(shí)計(jì)算

GoLang中defer的作用域不是塊,而是函數(shù),是出于怎樣的考慮

舉個(gè)例子,如果我們的代碼邏輯是下面這樣的:

打開數(shù)據(jù)庫(kù)連接

defer 關(guān)閉連接

defer 刪除數(shù)據(jù)

因?yàn)橐话鉪efer定義是和打開連接并列的,打開文件,打開連接之后就定義了defer, 如果這之后你的defer是基于這個(gè)連接做的事情,那么如果先進(jìn)先執(zhí)行的話就會(huì)錯(cuò)誤了。這就是當(dāng)初Go設(shè)計(jì)defer的時(shí)候考慮的問題。

這里順帶提醒一下defer是存在一些小坑的,就是defer里面的變量是申明的時(shí)候就copy的,不會(huì)隨著后面的函數(shù)邏輯改變而改變,除非你用指針類型。

package main

import "fmt"

func main() {

var whatever [5]struct{}

for i := range whatever {

fmt.Println(i)

}

for i := range whatever {

defer func() { fmt.Println(i) }()

}

for i := range whatever {

defer func(n int) { fmt.Println(n) }(i)

}

}

3.6 Go語言函數(shù)的延遲調(diào)用(Deferred Code)

在以下這段代碼中,我們操作一個(gè)文件,無論成功與否都需要關(guān)閉文件句柄。這里在三處不同的位置都調(diào)用了file.Close()方法,代碼顯得非常冗余。

我們利用延遲調(diào)用來優(yōu)化代碼。定義后的defer代碼,會(huì)在return之前返回,讓代碼顯得更加緊湊,且可讀性變強(qiáng),對(duì)上面的代碼改造如下:

我們通過這個(gè)示例來看一下延遲調(diào)用與正常代碼之間的執(zhí)行順序

先簡(jiǎn)單分析一下代碼邏輯:

從輸出中,我們可以觀察到如下現(xiàn)象:

從這個(gè)實(shí)例中,我們很明顯觀察到,defer語句是在return之前執(zhí)行

如果一個(gè)函數(shù)內(nèi)定義了多個(gè)defer,則調(diào)用順序?yàn)長(zhǎng)IFO(后進(jìn)先出)方式執(zhí)行。

仍然是相同的例子,但是在TestDefer中我們定義了三個(gè)defer輸出,根據(jù)LIFO原則,輸出的順序是3rd-2nd-1st,根據(jù)最后的結(jié)果,也是逆向向上執(zhí)行defer輸出。

就在整理這篇筆記的時(shí)候,發(fā)現(xiàn)了自己的認(rèn)知誤區(qū),主要是本節(jié)實(shí)例三中發(fā)現(xiàn)的,先來看一下英文的描述:

對(duì)于上面的這段話的理解:

下面是代碼執(zhí)行輸出,我們來一起分析一下:

雖然在a()函數(shù)內(nèi),顯示的返回了10,但是main函數(shù)中得到的結(jié)果是defer函數(shù)自增后的結(jié)果,我們來分析一下代碼:

在這篇文章的上一版,我曾經(jīng)嘗試用指針取解釋defer修改返回值的類型,但是感覺不夠透徹,也讓閱讀者非常困惑,索性參考了一下go官方blog中的一篇文章,在此基礎(chǔ)上進(jìn)行了擴(kuò)展。如需要閱讀原文,可以參考下面的文章。

本文標(biāo)題:go語言defer詳解 go語言 func
網(wǎng)站地址:http://bm7419.com/article22/dohhjcc.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供用戶體驗(yàn)、軟件開發(fā)靜態(tài)網(wǎng)站、定制網(wǎng)站營(yíng)銷型網(wǎng)站建設(shè)、網(wǎng)站收錄

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

h5響應(yīng)式網(wǎng)站建設(shè)