Golang defer 使用時的坑

2021-07-12 05:15:35 字數 1846 閱讀 5966

defer是golang語言中的關鍵字,用於資源的釋放,會在函式返回之前進行呼叫。一般採用如下模式:

f,err := os.open(filename)

if err != nil

defer f.close()

如果有多個defer表示式,呼叫順序類似於棧,越後面的defer表示式越先被呼叫。

不過如果對defer的了解不夠深入,使用起來可能會踩到一些坑,尤其是跟帶命名的返回引數一起使用時。在講解defer的實現之前先看一看使用defer容易遇到的問題。

先來看看幾個例子。例1:

func f() (result int) ()

return 0

}

例2:

func f() (r int) ()

return t

}

例3:

func f() (r int) (r)

return 1

}

請讀者先不要執行**,在心裡跑一遍結果,然後去驗證。

例1的正確答案不是0,例2的正確答案不是10,如果例3的正確答案不是6......

defer是在return之前執行的。這個在 官方文件中是明確說明了的。要使用defer時不踩坑,最重要的一點就是要明白,return ***這一條語句並不是一條原子指令!

函式返回的過程是這樣的:先給返回值賦值,然後呼叫defer表示式,最後才是返回到呼叫函式中。

defer表示式可能會在設定函式返回值之後,在返回到呼叫函式之前,修改返回值,使最終的函式返回值與你想象的不一致。

其實使用defer時,用乙個簡單的轉換規則改寫一下,就不會迷糊了。改寫規則是將return語句拆成兩句寫,return ***會被改寫成:

返回值 = ***

呼叫defer函式

空的return

先看例1,它可以改寫成這樣:

func f() (result int) ()

return

}

所以這個返回值是1。

再看例2,它可以改寫成這樣:

func f() (r int) 

return //空的return指令

}

所以這個的結果是5。

最後看例3,它改寫後變成:

func f() (r int) (r)

return //空的return

}

所以這個例子的結果是1。

defer確實是在return之前呼叫的。但表現形式上卻可能不像。本質原因是return ***語句並不是一條原子指令,defer被插入到了賦值 與 ret之間,因此可能有機會改變最終的返回值。

defer關鍵字的實現跟go關鍵字很類似,不同的是它呼叫的是runtime.deferproc而不是runtime.newproc。

在defer出現的地方,插入了指令call runtime.deferproc,然後在函式返回之前的地方,插入指令call runtime.deferreturn。

普通的函式返回時,彙編**類似:

add xx sp

return

如果其中包含了defer語句,則彙編**是:

call runtime.deferreturn,

add xx sp

return

goroutine的控制結構中,有一張表記錄defer,呼叫runtime.deferproc時會將需要defer的表示式記錄在表中,而在呼叫runtime.deferreturn的時候,則會依次從defer表中出棧並執行。

golang defer使用 資源關閉時候多用

go語言中有種不錯的設計,即延遲 defer 語句,你可以在函式中新增多個defer語句。當函式執行到最後時,這些defer語句會按照逆序執行,最後該函式返回。特別是當你在進行一些開啟資源的操作時,遇到錯誤需要提前返回,在返回前你需要關閉相應的資源,不然很容易造成資源洩露等問題。如下 所示,我們一般...

golang defer效能損耗和實際使用場景

我們常常聽到別人說 defer 在棧退出時執行,會有效能損耗,盡量不要用。前面的部落格 defer原理 我們分析了defer延遲呼叫的底層實現原理 下面我們就基於那篇原理分析文章,來分析一下 defer 延遲呼叫的效能損耗。package main import sync testing var l...

background size使用時的注意點

background size要寫在background image後面,否則background size不會生效 comment clist item code 這樣寫,background size是不生效的 必須這樣寫 comment clist item code 如果乙個後面的乙個樣式修...