本文由go語(yǔ)言教程欄目給大家介紹,主題是關(guān)于go defer的學(xué)習(xí)使用,希望對(duì)需要的朋友有所幫助!
什么是defer?
在Go中,一個(gè)函數(shù)調(diào)用可以跟在一個(gè)defer
關(guān)鍵字后面,形成一個(gè)延遲函數(shù)調(diào)用。
當(dāng)一個(gè)函數(shù)調(diào)用被延遲后,它不會(huì)立即被執(zhí)行。它將被推入由當(dāng)前協(xié)程維護(hù)的一個(gè)延遲調(diào)用堆棧。 當(dāng)一個(gè)函數(shù)調(diào)用(可能是也可能不是一個(gè)延遲調(diào)用)返回并進(jìn)入它的退出階段后,所有在此函數(shù)調(diào)用中已經(jīng)被推入的延遲調(diào)用將被按照它們被推入堆棧的順序逆序執(zhí)行。 當(dāng)所有這些延遲調(diào)用執(zhí)行完畢后,此函數(shù)調(diào)用也就真正退出了。
舉個(gè)簡(jiǎn)單的例子:
package mainimport "fmt"func sum(a, b int) { defer fmt.Println("sum函數(shù)即將返回") defer fmt.Println("sum函數(shù)finished") fmt.Printf("參數(shù)a=%v,參數(shù)b=%v,兩數(shù)之和為%vn", a, b, a+b)}func main() { sum(1, 2)}
output:
參數(shù)a=1,參數(shù)b=2,兩數(shù)之和為3 sum函數(shù)finished sum函數(shù)即將返回
事實(shí)上,每個(gè)協(xié)程維護(hù)著兩個(gè)調(diào)用堆棧。
- 一個(gè)是正常的函數(shù)調(diào)用堆棧。在此堆棧中,相鄰的兩個(gè)調(diào)用存在著調(diào)用關(guān)系。晚進(jìn)入堆棧的調(diào)用被早進(jìn)入堆棧的調(diào)用所調(diào)用。 此堆棧中最早被推入的調(diào)用是對(duì)應(yīng)協(xié)程的啟動(dòng)調(diào)用。
- 另一個(gè)堆棧是上面提到的延遲調(diào)用堆棧。處于延遲調(diào)用堆棧中的任意兩個(gè)調(diào)用之間不存在調(diào)用關(guān)系。
defer函數(shù)參數(shù)估值
- 對(duì)于一個(gè)延遲函數(shù)調(diào)用,它的實(shí)參是在此調(diào)用被推入延遲調(diào)用堆棧的時(shí)候被估值的。
- 一個(gè)匿名函數(shù)體內(nèi)的表達(dá)式是在此函數(shù)被執(zhí)行的時(shí)候才會(huì)被逐個(gè)估值的,不管此函數(shù)是被普通調(diào)用還是延遲調(diào)用。
例子1:
package mainimport "fmt"func Print(a int) {fmt.Println("defer函數(shù)中a的值=", a)}func main() {a := 10defer Print(a)a = 1000fmt.Println("a的值=", a)}
output:
a的值= 1000 defer函數(shù)中a的值= 10
defer Print(a) 被加入到延遲調(diào)用堆棧的時(shí)候,a 的值是5,故defer Print(a) 輸出的結(jié)果為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
第一個(gè)匿名函數(shù)循環(huán)中的 i 是在 fmt.Println函數(shù)調(diào)用被推入延遲調(diào)用堆棧的時(shí)候估的值,因此輸出結(jié)果是 2,1,0 , 第二個(gè)匿名函數(shù)中的 i 是匿名函數(shù)調(diào)用退出階段估的值(此時(shí) i 已經(jīng)變成3了),故結(jié)果輸出:3,3,3。
其實(shí)對(duì)第二個(gè)匿名函數(shù)調(diào)用略加修改,就能使它輸出和匿名函數(shù)一相同的結(jié)果:
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)和恢復(fù)(defer + recover)
Go不支持異常拋出和捕獲,而是推薦使用返回值顯式返回錯(cuò)誤。 不過(guò),Go支持一套和異常拋出/捕獲類(lèi)似的機(jī)制。此機(jī)制稱(chēng)為恐慌/恢復(fù)(panic/recover)機(jī)制。
我們可以調(diào)用內(nèi)置函數(shù)panic
來(lái)產(chǎn)生一個(gè)恐慌以使當(dāng)前協(xié)程進(jìn)入恐慌狀況。
進(jìn)入恐慌狀況是另一種使當(dāng)前函數(shù)調(diào)用開(kāi)始返回的途徑。 一旦一個(gè)函數(shù)調(diào)用產(chǎn)生一個(gè)恐慌,此函數(shù)調(diào)用將立即進(jìn)入它的退出階段,在此函數(shù)調(diào)用中被推入堆棧的延遲調(diào)用將按照它們被推入的順序逆序執(zhí)行。
通過(guò)在一個(gè)延遲函數(shù)調(diào)用之中調(diào)用內(nèi)置函數(shù)recover
,當(dāng)前協(xié)程中的一個(gè)恐慌可以被消除,從而使得當(dāng)前協(xié)程重新進(jìn)入正常狀況。
在一個(gè)處于恐慌狀況的協(xié)程退出之前,其中的恐慌不會(huì)蔓延到其它協(xié)程。 如果一個(gè)協(xié)程在恐慌狀況下退出,它將使整個(gè)程序崩潰??聪旅娴膬蓚€(gè)例子:
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函數(shù)發(fā)生panic(除數(shù)為0),因?yàn)樗趨f(xié)程沒(méi)有恐慌恢復(fù)機(jī)制,導(dǎo)致整個(gè)程序崩潰。
如果p函數(shù)所在協(xié)程加上恐慌恢復(fù)(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("恐慌被恢復(fù)了:", v) } }() fmt.Println(p(1, 0)) }() time.Sleep(time.Second) fmt.Println("程序正常退出~~~")}
output:
恐慌被恢復(fù)了: runtime error: integer pide by zero 程序正常退出~~~