這篇文章主要介紹了Golang Recover中有哪些坑需要注意,具有一定借鑒價值,需要的朋友可以參考下。希望大家閱讀完這篇文章后大有收獲。下面讓小編帶著大家一起了解一下。
1.error
Golang被詬病非常多的一點就是缺少強大方便的異常處理機制,大部分高級編程語言,比如Java、PHP、Python等都擁有一種try catch機制,這種異常捕獲機制可以非常方便的處理程序運行中可能出現的各種意外情況。
嚴格來說,在Go里面,錯誤和異常是2種不同的類型,錯誤一般是指程序產生的邏輯錯誤,或者意料之中的意外情況,而且異常一般就是panic,比如角標越界、段錯誤。
對于錯誤,Golang采用了一種非常原始的手段,我們必須手動處理可能產生的每一個錯誤,一般會把錯誤返回給調用方,下面這種寫法在Go里面十分常見:
package mainimport (
"errors"
"fmt")func main() {
s, err := say()
if err != nil {
fmt.Printf("%s\n", err.Error())
} else {
fmt.Printf("%s\n", s)
}}func say() (string, error) {
// do something
return "", errors.New("something error")}復制代碼這種寫法最大的問題就是每一個error都需要判斷處理,非常繁瑣,如果使用try catch機制,我們就可以統一針對多個函數調用可能產生的錯誤做處理,節省一點代碼和時間。不過咱們今天不是來討論Go的異常錯誤處理機制的,這里只是簡單說一下。
2.panic
一般錯誤都是顯示的,程序明確返回的,而異常往往是隱示的,不可預測的,比如下面的代碼:
package mainimport "fmt"func main() {
fmt.Printf("%d\n", cal(1,2))
fmt.Printf("%d\n", cal(5,2))
fmt.Printf("%d\n", cal(5,0)) //panic: runtime error: integer pide by zero
fmt.Printf("%d\n", cal(9,5))}func cal(a, b int) int {
return a / b}復制代碼在執行第三個計算的時候會發生一個panic,這種錯誤會導致程序退出,下面的代碼的就無法執行了。當然你可以說這種錯誤理論上是可以預測的,我們只要在cal函數內部做好處理就行了。
然而實際開發中,會發生panic的地方可能特別多,而且不是這種一眼就能看出來的,在Web服務中,這樣的panic會導致整個Web服務掛掉,特別危險。
3.recover
雖然沒有try catch機制,Go其實有一種類似的recover機制,功能弱了點,用法很簡單:
package mainimport "fmt"func main() {
fmt.Printf("%d\n", cal(1, 2))
fmt.Printf("%d\n", cal(5, 2))
fmt.Printf("%d\n", cal(5, 0))
fmt.Printf("%d\n", cal(9, 2))}func cal(a, b int) int {
defer func() {
if err := recover(); err != nil {
fmt.Printf("%s\n", err)
}
}()
return a / b}復制代碼首先,大家得理解defer的作用,簡單說defer就類似于面向對象里面的析構函數,在這個函數終止的時候會執行,即使是panic導致的終止。
所以,在cal函數里面每次終止的時候都會檢查有沒有異常產生,如果產生了我們可以處理,比如說記錄日志,這樣程序還可以繼續執行下去。
4.注意的坑
一般defer recover這種機制經常用在常駐進程的應用,比如Web服務,在Go里面,每一個Web請求都會分配一個goroutine去處理,在沒有做任何處理的情況下,假如某一個請求發生了panic,就會導致整個服務掛掉,這是不可接受的,所以在Web應用里面必須使用recover保證即使某一個請求發生錯誤也不影響其它請求。
這里我使用一小段代碼模擬一下:
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) //開啟多個協程
}
for {
select {}
}}func run(num int) {
//模擬請求錯誤
if num%5 == 0 {
panic("請求出錯")
}
fmt.Printf("%d\n", num)}復制代碼上面這段代碼無法完整執行下去,因為其中某一個協程必然會發生panic,從而導致整個應用掛掉,其它協程也停止執行。
解決方法和上面一樣,我們只需要在run函數里面加入defer recover,整個程序就會非常健壯,即使發生panic,也會完整的執行下去。
func run(num int) {
defer func() {
if err := recover();err != nil {
fmt.Printf("%s\n", err)
}
}()
if num%5 == 0 {
panic("請求出錯")
}
fmt.Printf("%d\n", num)}復制代碼上面的代碼只是演示,真正的坑是:如果你在run函數里面又啟動了其它協程,這個協程發生的panic是無法被recover的,還是會導致整個進程掛掉,我們改造了一下上面的例子:
func run(num int) {
defer func() {
if err := recover(); err != nil {
fmt.Printf("%s\n", err)
}
}()
if num%5 == 0 {
panic("請求出錯")
}
go myPrint(num)}func myPrint(num int) {
if num%4 == 0 {
panic("請求又出錯了")
}
fmt.Printf("%d\n", num)}復制代碼我在run函數里面又通過協程的方式調用了另一個函數,而這個函數也會發生panic,你會發現整個程序也掛了,即使run函數有recover也沒有任何作用,這意味著我們還需要在myPrint函數里面加入recover。但是如果你不使用協程的方式調用myPrint函數,直接調用的話還是可以捕獲recover的。
總結一下就是defer recover這種機制只是針對當前函數和以及直接調用的函數可能產生的panic,它無法處理其調用產生的其它協程的panic,這一點和try catch機制不一樣。
理論上講,所有使用協程的地方都必須做defer recover處理,這樣才能保證你的應用萬無一失,不過開發中可以根據實際情況而定,對于一些不可能出錯的函數加了還影響性能。
Go的Web服務也是一樣,默認的recover機制只能捕獲一層,如果你在這個請求的處理中又使用了其它協程,那么必須非常慎重,畢竟只要發生一個panic,整個Web服務就會掛掉。
感謝你能夠認真閱讀完這篇文章,希望小編分享Golang Recover中有哪些坑需要注意內容對大家有幫助,同時也希望大家多多支持億速云,關注億速云行業資訊頻道,遇到問題就找億速云,詳細的解決方法等著你來學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。