go語言 defer 高階

2021-09-07 20:47:14 字數 4290 閱讀 3013

go語言defer語句的用法

defer後面必須是函式呼叫語句,不能是其他語句,否則編譯器會出錯。

package main

import "log"

func foo(n int) int func main()

這個例子中defer後面使用的是n++指令,不是乙個函式呼叫語句,編譯器就報錯:

# command-line-arguments

./main.go:6: expression in defer must be function call

./main.go:6: syntax error: unexpected ++ at end of statement

defer後面的函式在defer語句所在的函式執行結束的時候會被呼叫;我們檢視一下彙編嗎,看看defer是在什麼時候被執行的:

定義兩個函式foo1和foo2,功能和**都是一樣,只是其中乙個包含defer語句,另乙個沒有。

func foo1(i int) int  func foo2(i int) int  

這是foo1的彙編**:

func foo1(i int) int  

再看foo2的彙編**:

func foo2(i int) int  

通過比較很容易看出foo2有兩處需要注意,第一處是defer foo()語句的翻譯,這個翻譯我沒有細看懂,我猜是準備foo的函式引數(如果有),然後儲存這些引數值和foo的位址,註冊到系統(runtime.deferproc);另一處是return指令的翻譯,return指令的執行分三步,第一步拷貝return值到返回值記憶體位址,第二步會呼叫runtime.deferreturn去執行前面註冊的defer函式,第三部再執行ret彙編指令。

有兩個常見的defer語句應用場景是:

func copyfile(dstname, srcname string) (written int64, err error)  defer src.close() dst, err := os.create(dstname) if err != nil  defer dst.close() // other codes return io.copy(dst, src) } 

在開啟輸入檔案輸出檔案後,不管後面的**流程如何影響,這兩個檔案能夠被自動關閉。

func foo(...)  

確保mu鎖能夠在函式foo退出之後自動釋放。

我們注意到defer函式的執行是在defer指令所在函式的執行結束之後,那麼如何才能在所在函式的中間就釋放呢,比如前面例子,在foo入口鎖住了lock,而如果foo後半段的**執行時間比較長,而此時又不需要繼續保持住鎖,該怎麼辦呢?

func foo()  // time-consuming operating with object ... } 

我們希望能在time-consuming operation 之前就釋放鎖,而不是等到整個foo返回。這有兩個辦法,乙個是根據邏輯,把foo拆分兩部分,前半部分需要鎖,後半部分不需要鎖;另乙個辦法是使用匿名函式:

package main

import "log"

import "time" import "sync" var mu sync.mutex func lock() func unlock() func foo() int () time.sleep(1 * time.second) log.printf("return") return 0; } func main()

執行結果:

$ ./main 

2017/09/30 22:18:58 lock 2017/09/30 22:18:58 inner 2017/09/30 22:18:58 unlock 2017/09/30 22:18:59 return 2017/09/30 22:18:59 r= 0

從日誌我們可以看出mu鎖在sleep語句之前已經被釋放了,而不是需要等到foo函式結束的時候才釋放。

如果函式裡面有多條defer指令,他們的執行順序是反序,即後定義的defer先執行。

package main

import "log"

import "time" func foo(n int) int func main()

執行結果如下,可以看出他們的呼叫順序:

2017/09/30 19:22:03 3333

2017/09/30 19:22:03 2222

2017/09/30 19:22:03 1111

defer函式的引數是在defer語句出現的位置做計算的,而不是在函式執行的時候做計算的,即所在函式結束的時候計算的。

package main

import "log"

func foo(n int) int func main()

其執行結果是:

2017/09/30 19:25:10 n1= 100

2017/09/30 19:25:10 n2= 200

2017/09/30 19:25:10 n= 100

可以看到defer函式的位置時n的值為100,儘管在函式foo結束的時候n的值已經是200了,但是defer語句本身所處的位置時刻,即foo函式入口時n為100,所以最終defer函式列印出來的n值為100。

前面我們提到defer後面只能是一條函式呼叫指令;而實際情況下經常會需要邏輯執行,會有分支,條件,而不是簡單的乙個log.print指令;那怎麼處理這種情況呢,我們可以把這些邏輯指令一起定義成乙個函式,然後再呼叫這些函式就行了,命名函式或者匿名函式都可以,下面是乙個匿名函式的例子:

package main

import "log"

import _ "time" func foo(n int) int () n += 100 log.println("n2=", n) return n } func main()

執行結果:

2017/09/30 19:30:58 n1= 100

2017/09/30 19:30:58 n2= 200

2017/09/30 19:30:58 n= 300

眼尖的同學會發現其中的問題;為什麼n列印出來是300呢,不是明明說好defer函式的引數值在它出現時候計算,而不是在執行的時候計算的嗎,n應該列印出200才對啊?

同學,仔細看一下原文:defer函式的引數在defer語句出現的位置計算,不是在defer函式執行的時刻計算;人家明明說的很清楚,defer函式的引數,請問這裡n是引數嗎,不是哎,這裡引用的是宿主函式的區域性變數,而不是引數;所以它拿到的是執行時刻的值。

這就引發出下乙個注意事項。

package main

import "log"

func foo1(i *int) int () log.printf("i=%d", *i) return *i } func foo2(i *int) (r int) () log.printf("i=%d", *i) return *i } func main()

執行結果為:

$ go build main.go && ./main 

2017/09/30 20:01:00 i=100 2017/09/30 20:01:00 i=300, r=100 2017/09/30 20:01:00 i=100 2017/09/30 20:01:00 i=100, r=300

這個例子其實有一點拗口的。

foo1 return指令前(i==100, ret==0),return指令後(i==100, ret=100),然後呼叫defer函式後(i==300,r==100),defer函式增加了i;main函式收到(i==300, r==100)

foo2 return指令前(i==100, ret==0),return指令後(i==100, ret=100),然後呼叫defer函式後(i==100,r==300),defer函式增加了ret;main函式收到(i==100, r==300)

go語言 defer 高階

go語言defer語句的用法 defer後面必須是函式呼叫語句,不能是其他語句,否則編譯器會出錯。package main import log func foo n int int func main 這個例子中defer後面使用的是n 指令,不是乙個函式呼叫語句,編譯器就報錯 command l...

Go語言defer學習小結

延時呼叫函式語法 defer func name param list defer func 關鍵字defer修飾的函式,值得關注的有以下幾點 1 函式會被推遲到包含這個defer語句的函式即將返回前才被呼叫執行。這點需要理解defer的工作機制,大致為defer出現的地方,插入指令call run...

go語言defer的使用

go語言的defer 延遲執行 語句,是在函式結束前執行,而如果在函式中有多個defer語句時,會像乙個堆疊一樣,先進後出,後進先出。defer語句在進行一些開啟資源的操作時,遇到錯誤需要提前返回,在返回前你需要關閉相應的資源,不然很容易造成資源洩露等問題上很試用。舉個defer語句簡單的使用如下 ...