這篇文章將為大家詳細講解有關在Go中defer有什么用,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。
什么是defer?
在Go中,一個函數調用可以跟在一個defer
關鍵字后面,形成一個延遲函數調用。
當一個函數調用被延遲后,它不會立即被執行。它將被推入由當前協程維護的一個延遲調用堆棧。 當一個函數調用(可能是也可能不是一個延遲調用)返回并進入它的退出階段后,所有在此函數調用中已經被推入的延遲調用將被按照它們被推入堆棧的順序逆序執行。 當所有這些延遲調用執行完畢后,此函數調用也就真正退出了。
舉個簡單的例子:
package mainimport "fmt"func sum(a, b int) { defer fmt.Println("sum函數即將返回") defer fmt.Println("sum函數finished") fmt.Printf("參數a=%v,參數b=%v,兩數之和為%v\n", a, b, a+b)}func main() { sum(1, 2)}
output:
參數a=1,參數b=2,兩數之和為3 sum函數finished sum函數即將返回
事實上,每個協程維護著兩個調用堆棧。
一個是正常的函數調用堆棧。在此堆棧中,相鄰的兩個調用存在著調用關系。晚進入堆棧的調用被早進入堆棧的調用所調用。 此堆棧中最早被推入的調用是對應協程的啟動調用。
另一個堆棧是上面提到的延遲調用堆棧。處于延遲調用堆棧中的任意兩個調用之間不存在調用關系。
defer函數參數估值
對于一個延遲函數調用,它的實參是在此調用被推入延遲調用堆棧的時候被估值的。
一個匿名函數體內的表達式是在此函數被執行的時候才會被逐個估值的,不管此函數是被普通調用還是延遲調用。
例子1:
package mainimport "fmt"func Print(a int) {fmt.Println("defer函數中a的值=", a)}func main() {a := 10defer Print(a)a = 1000fmt.Println("a的值=", a)}
output:
a的值= 1000 defer函數中a的值= 10
defer Print(a) 被加入到延遲調用堆棧的時候,a 的值是5,故defer Print(a) 輸出的結果為5
例子2:
package mainimport "fmt"func main() { func() { for i := 0; i < 3; i++ { defer fmt.Println("a=", i) } }() fmt.Println() func() { for i := 0; i < 3; i++ { defer func() { fmt.Println("b=", i) }() } }()}
output:
a= 2 a= 1 a= 0 b= 3 b= 3 b= 3
第一個匿名函數循環中的 i 是在 fmt.Println函數調用被推入延遲調用堆棧的時候估的值,因此輸出結果是 2,1,0 , 第二個匿名函數中的 i 是匿名函數調用退出階段估的值(此時 i 已經變成3了),故結果輸出:3,3,3。
其實對第二個匿名函數調用略加修改,就能使它輸出和匿名函數一相同的結果:
package mainimport "fmt"func main() { func() { for i := 0; i < 3; i++ { defer fmt.Println("a=", i) } }() fmt.Println() func() { for i := 0; i < 3; i++ { defer func(i int) { fmt.Println("b=", i) }(i) } }()}
output:
a= 2 a= 1 a= 0 b= 2 b= 1 b= 0
恐慌(panic)和恢復(defer + recover)
Go不支持異常拋出和捕獲,而是推薦使用返回值顯式返回錯誤。 不過,Go支持一套和異常拋出/捕獲類似的機制。此機制稱為恐慌/恢復(panic/recover)機制。
我們可以調用內置函數panic
來產生一個恐慌以使當前協程進入恐慌狀況。
進入恐慌狀況是另一種使當前函數調用開始返回的途徑。 一旦一個函數調用產生一個恐慌,此函數調用將立即進入它的退出階段,在此函數調用中被推入堆棧的延遲調用將按照它們被推入的順序逆序執行。
通過在一個延遲函數調用之中調用內置函數recover
,當前協程中的一個恐慌可以被消除,從而使得當前協程重新進入正常狀況。
在一個處于恐慌狀況的協程退出之前,其中的恐慌不會蔓延到其它協程。 如果一個協程在恐慌狀況下退出,它將使整個程序崩潰??聪旅娴膬蓚€例子:
package mainimport ( "fmt" "time")func p(a, b int) int { return a / b}func main() { go func() { fmt.Println(p(1, 0)) }() time.Sleep(time.Second) fmt.Println("程序正常退出~~~")}
output:
panic: runtime error: integer pide by zero goroutine 6 [running]: main.p(...) /Users/didi/Desktop/golang/defer.go:9 main.main.func1() /Users/didi/Desktop/golang/defer.go:14 +0x12 created by main.main /Users/didi/Desktop/golang/defer.go:13 +0x39exit status 2
p函數發生panic(除數為0),因為所在協程沒有恐慌恢復機制,導致整個程序崩潰。
如果p函數所在協程加上恐慌恢復(defer + recover),程序便可正常退出。
package mainimport ( "fmt" "time")func p(a, b int) int { return a / b}func main() { go func() { defer func() { v := recover() if v != nil { fmt.Println("恐慌被恢復了:", v) } }() fmt.Println(p(1, 0)) }() time.Sleep(time.Second) fmt.Println("程序正常退出~~~")}
output:
恐慌被恢復了: runtime error: integer pide by zero 程序正常退出~~~
關于“在Go中defer有什么用”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,使各位可以學到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。