下面由golang教程欄目給大家介紹Golang Recover的一個(gè)小坑,希望對(duì)需要的朋友有所幫助!
1.error
Golang被詬病非常多的一點(diǎn)就是缺少?gòu)?qiáng)大方便的異常處理機(jī)制,大部分高級(jí)編程語(yǔ)言,比如Java、PHP、Python等都擁有一種try catch機(jī)制,這種異常捕獲機(jī)制可以非常方便的處理程序運(yùn)行中可能出現(xiàn)的各種意外情況。
嚴(yán)格來(lái)說(shuō),在Go里面,錯(cuò)誤和異常是2種不同的類型,錯(cuò)誤一般是指程序產(chǎn)生的邏輯錯(cuò)誤,或者意料之中的意外情況,而且異常一般就是panic,比如角標(biāo)越界、段錯(cuò)誤。
對(duì)于錯(cuò)誤,Golang采用了一種非常原始的手段,我們必須手動(dòng)處理可能產(chǎn)生的每一個(gè)錯(cuò)誤,一般會(huì)把錯(cuò)誤返回給調(diào)用方,下面這種寫(xiě)法在Go里面十分常見(jiàn):
package mainimport ( "errors" "fmt")func main() { s, err := say() if err != nil { fmt.Printf("%sn", err.Error()) } else { fmt.Printf("%sn", s) }}func say() (string, error) { // do something return "", errors.New("something error")}復(fù)制代碼
這種寫(xiě)法最大的問(wèn)題就是每一個(gè)error都需要判斷處理,非常繁瑣,如果使用try catch機(jī)制,我們就可以統(tǒng)一針對(duì)多個(gè)函數(shù)調(diào)用可能產(chǎn)生的錯(cuò)誤做處理,節(jié)省一點(diǎn)代碼和時(shí)間。不過(guò)咱們今天不是來(lái)討論Go的異常錯(cuò)誤處理機(jī)制的,這里只是簡(jiǎn)單說(shuō)一下。
2.panic
一般錯(cuò)誤都是顯示的,程序明確返回的,而異常往往是隱示的,不可預(yù)測(cè)的,比如下面的代碼:
package mainimport "fmt"func main() { fmt.Printf("%dn", cal(1,2)) fmt.Printf("%dn", cal(5,2)) fmt.Printf("%dn", cal(5,0)) //panic: runtime error: integer pide by zero fmt.Printf("%dn", cal(9,5))}func cal(a, b int) int { return a / b}復(fù)制代碼
在執(zhí)行第三個(gè)計(jì)算的時(shí)候會(huì)發(fā)生一個(gè)panic,這種錯(cuò)誤會(huì)導(dǎo)致程序退出,下面的代碼的就無(wú)法執(zhí)行了。當(dāng)然你可以說(shuō)這種錯(cuò)誤理論上是可以預(yù)測(cè)的,我們只要在cal函數(shù)內(nèi)部做好處理就行了。
然而實(shí)際開(kāi)發(fā)中,會(huì)發(fā)生panic的地方可能特別多,而且不是這種一眼就能看出來(lái)的,在Web服務(wù)中,這樣的panic會(huì)導(dǎo)致整個(gè)Web服務(wù)掛掉,特別危險(xiǎn)。
3.recover
雖然沒(méi)有try catch機(jī)制,Go其實(shí)有一種類似的recover機(jī)制,功能弱了點(diǎn),用法很簡(jiǎn)單:
package mainimport "fmt"func main() { fmt.Printf("%dn", cal(1, 2)) fmt.Printf("%dn", cal(5, 2)) fmt.Printf("%dn", cal(5, 0)) fmt.Printf("%dn", cal(9, 2))}func cal(a, b int) int { defer func() { if err := recover(); err != nil { fmt.Printf("%sn", err) } }() return a / b}復(fù)制代碼
首先,大家得理解defer的作用,簡(jiǎn)單說(shuō)defer就類似于面向?qū)ο罄锩娴奈鰳?gòu)函數(shù),在這個(gè)函數(shù)終止的時(shí)候會(huì)執(zhí)行,即使是panic導(dǎo)致的終止。
所以,在cal函數(shù)里面每次終止的時(shí)候都會(huì)檢查有沒(méi)有異常產(chǎn)生,如果產(chǎn)生了我們可以處理,比如說(shuō)記錄日志,這樣程序還可以繼續(xù)執(zhí)行下去。
4.注意的坑
一般defer recover這種機(jī)制經(jīng)常用在常駐進(jìn)程的應(yīng)用,比如Web服務(wù),在Go里面,每一個(gè)Web請(qǐng)求都會(huì)分配一個(gè)goroutine去處理,在沒(méi)有做任何處理的情況下,假如某一個(gè)請(qǐng)求發(fā)生了panic,就會(huì)導(dǎo)致整個(gè)服務(wù)掛掉,這是不可接受的,所以在Web應(yīng)用里面必須使用recover保證即使某一個(gè)請(qǐng)求發(fā)生錯(cuò)誤也不影響其它請(qǐng)求。
這里我使用一小段代碼模擬一下:
package mainimport ( "fmt")func main() { requests := []int{12, 2, 3, 41, 5, 6, 1, 12, 3, 4, 2, 31} for n := range requests { go run(n) //開(kāi)啟多個(gè)協(xié)程 } for { select {} }}func run(num int) { //模擬請(qǐng)求錯(cuò)誤 if num%5 == 0 { panic("請(qǐng)求出錯(cuò)") } fmt.Printf("%dn", num)}復(fù)制代碼
上面這段代碼無(wú)法完整執(zhí)行下去,因?yàn)槠渲心骋粋€(gè)協(xié)程必然會(huì)發(fā)生panic,從而導(dǎo)致整個(gè)應(yīng)用掛掉,其它協(xié)程也停止執(zhí)行。
解決方法和上面一樣,我們只需要在run函數(shù)里面加入defer recover,整個(gè)程序就會(huì)非常健壯,即使發(fā)生panic,也會(huì)完整的執(zhí)行下去。
func run(num int) { defer func() { if err := recover();err != nil { fmt.Printf("%sn", err) } }() if num%5 == 0 { panic("請(qǐng)求出錯(cuò)") } fmt.Printf("%dn", num)}復(fù)制代碼
上面的代碼只是演示,真正的坑是:如果你在run函數(shù)里面又啟動(dòng)了其它協(xié)程,這個(gè)協(xié)程發(fā)生的panic是無(wú)法被recover的,還是會(huì)導(dǎo)致整個(gè)進(jìn)程掛掉,我們改造了一下上面的例子:
func run(num int) { defer func() { if err := recover(); err != nil { fmt.Printf("%sn", err) } }() if num%5 == 0 { panic("請(qǐng)求出錯(cuò)") } go myPrint(num)}func myPrint(num int) { if num%4 == 0 { panic("請(qǐng)求又出錯(cuò)了") } fmt.Printf("%dn", num)}復(fù)制代碼
我在run函數(shù)里面又通過(guò)協(xié)程的方式調(diào)用了另一個(gè)函數(shù),而這個(gè)函數(shù)也會(huì)發(fā)生panic,你會(huì)發(fā)現(xiàn)整個(gè)程序也掛了,即使run函數(shù)有recover也沒(méi)有任何作用,這意味著我們還需要在myPrint函數(shù)里面加入recover。但是如果你不使用協(xié)程的方式調(diào)用myPrint函數(shù),直接調(diào)用的話還是可以捕獲recover的。
總結(jié)一下就是defer recover這種機(jī)制只是針對(duì)當(dāng)前函數(shù)和以及直接調(diào)用的函數(shù)可能產(chǎn)生的panic,它無(wú)法處理其調(diào)用產(chǎn)生的其它協(xié)程的panic,這一點(diǎn)和try catch機(jī)制不一樣。
理論上講,所有使用協(xié)程的地方都必須做defer recover處理,這樣才能保證你的應(yīng)用萬(wàn)無(wú)一失,不過(guò)開(kāi)發(fā)中可以根據(jù)實(shí)際情況而定,對(duì)于一些不可能出錯(cuò)的函數(shù)加了還影響性能。
Go的Web服務(wù)也是一樣,默認(rèn)的recover機(jī)制只能捕獲一層,如果你在這個(gè)請(qǐng)求的處理中又使用了其它協(xié)程,那么必須非常慎重,畢竟只要發(fā)生一個(gè)panic,整個(gè)Web服務(wù)就會(huì)掛掉。
最后,總結(jié)一下,Go的異常處理機(jī)制雖然沒(méi)有很多其它語(yǔ)言高效,但是基本上還是能滿足需求,目前官方已經(jīng)在著完善這一點(diǎn),Go2可能會(huì)見(jiàn)到。
推薦:《go語(yǔ)言教程》