【Go學習】GO語言異常處理機制panic和recover分析

【Go學習】GO語言異常處理機制panic和recover分析

Golang 有2個內置的函數 panic() 和 recover(),用以報告和捕獲運行時發生的程序錯誤,與 error 不同,panic-recover 一般用在函數內部。一定要注意不要濫用 panic-recover,可能會導致性能問題,我一般只在未知輸入和不可靠請求時使用。

golang 的錯誤處理流程:當一個函數在執行過程中出現了異常或遇到 panic(),正常語句就會立即終止,然後執行 defer 語句,再報告異常信息,最後退出 goroutine。如果在 defer 中使用了 recover() 函數,則會捕獲錯誤信息,使該錯誤信息終止報告。

示例:

1 package main
2 
3 import (
4    "fmt"
5 )
6
7 func main() {
8    f()
9    fmt.Println("Returned normally from f.")
10 }
11
12
13 func f() {
14    fmt.Println("Calling g.")
15    g()
16    fmt.Println("Returned normally from g.")
17 }
18 
19 func g() {
20    panic("ERROR")
21 }
22

運行結果:

Calling g.
panic: ERROR

goroutine 1 [running]:
main.g()
    /home/james_xie/work/golangstudy/integer.go:20 +0x39
main.f()
    /home/james_xie/work/golangstudy/integer.go:15 +0x70
main.main()
    /home/james_xie/work/golangstudy/integer.go:8 +0x22
exit status 2

上面函數的函數g中的panic("ERROR")語句導致程序panic,正常語句就會立即終止,由於沒有defer語句,直接報告異常信息,然後退出整個goroutine ,可以通過panic的打印,看到程序在第20行的時候退出。下面修改一下,增加defer語句:

package main

import (
    "fmt"
)

func main() {
    f()
    fmt.Println("Returned normally from f.")
}

func f() {
    defer func(){
        fmt.Println("Defer in f")
    }()

    fmt.Println("Calling g.")
    g()
    fmt.Println("Returned normally from g.")
}

func g() {
    panic("ERROR")
}

運行結果:

Calling g.
Defer in f
panic: ERROR

goroutine 1 [running]:
main.g()
    /home/james_xie/work/golangstudy/integer.go:24 +0x39
main.f()
    /home/james_xie/work/golangstudy/integer.go:19 +0x90
main.main()
    /home/james_xie/work/golangstudy/integer.go:8 +0x22
exit status 2

這個時候可以看到,在報告異常信息之前,先執行了defer語句,然後再報告異常信息。下面我們在稍微修整下代碼:

package main

import (
    "fmt"
)

func main() {
    defer func(){
        fmt.Println("Defer before f")
    }()
    f()
    fmt.Println("Returned normally from f.")
}


func f() {
    defer func(){
        fmt.Println("Defer in f")
    }()

    fmt.Println("Calling g.")
    g()
    fmt.Println("Returned normally from g.")
}

func g() {
    panic("ERROR")
}

運行結果如下:

Calling g.
Defer in f
Defer before f
panic: ERROR

goroutine 1 [running]:
main.g()
    /home/james_xie/work/golangstudy/integer.go:27 +0x39
main.f()
    /home/james_xie/work/golangstudy/integer.go:22 +0x90
main.main()
    /home/james_xie/work/golangstudy/integer.go:11 +0x42
exit status 2

通過這個結果我們可以看出,defer語句的執行順序是逆序的,類似於數據結構中的棧(Stack)—-先進後出,我們繼續修改下代碼,添加對異常的處理,看看又會是個什麼情況:

package main

import (
    "fmt"
)

func main() {
    f()
    fmt.Println("Returned normally from f.")
}


func f() {
    defer func(){
        fmt.Println("Defer in f")
        if r := recover(); r != nil {
            fmt.Println("Recover panic : ",r)
        }
    }()

    fmt.Println("Calling g.")
    g()
    fmt.Println("Returned normally from g.")
}

func g() {
    panic("ERROR")
}

運行結果:

Calling g.
Defer in f
Recover panic :  ERROR
Returned normally from f.

通過上面的運行結果可以看出,如果在 defer 中使用了 recover() 函數,則會捕獲錯誤信息,使該錯誤信息終止報告。然後程序繼續運行,注意程序中這條語句fmt.Println("Returned normally from g.")沒有執行,由於在函數g中遇到panic函數,正常語句就會立即終止,所以函數g後面的語句不會執行,直接執行 defer 語句,在defer中通過recover()函數來捕獲錯誤信息,我們還是繼續修改代碼,看看又會出現什麼不同結果:

package main

import (
    "fmt"
)

func main() {
    defer func(){
        fmt.Println("Defer before f")
    }()
    f()
    fmt.Println("Returned normally from f.")
}


func f() {
    defer func(){
        fmt.Println("Defer in f")
        if r := recover(); r != nil {
            fmt.Println("Recover panic : ",r)
        }
    }()

    fmt.Println("Calling g.")
    g()
    fmt.Println("Returned normally from g.")
}

func g() {
    panic("ERROR")
}

運行結果:

Calling g.
Defer in f
Recover panic :  ERROR
Returned normally from f.
Defer before f

其實這個修改沒多大意義,因爲在函數f中的defer語句中,通過recover()函數來捕獲錯誤信息,使該錯誤信息終止報告,後面的程序正常執行,defer語句正常是在函數數返回前執行一些操作,所以這個時候的main函數中的defer語句的執行是在fmt.Println("Returned normally from f.")打印語句之後,main函數返回前執行的,最後我們在修改下代碼:

package main

import (
    "fmt"
)

func main() {
    defer func(){
        fmt.Println("Defer before f")
        if r := recover(); r != nil {
            fmt.Println("Recover panic : ",r)
        }
    }()
    f()
    fmt.Println("Returned normally from f.")
}


func f() {
    defer func(){
        fmt.Println("Defer in f")
    }()

    fmt.Println("Calling g.")
    g()
    fmt.Println("Returned normally from g.")
}

func g() {
    panic("ERROR")
}

運行結果:

Calling g.
Defer in f
Defer before f
Recover panic :  ERROR

上門的運行結果可以驗證,如果不調用recover()函數來捕獲錯誤信息,其則會一直報告錯誤信息,可以對比上門的修改來對比看。

最後可以總結如下:

panic:
1、內建函數
2、假如函數F中書寫了panic語句,會終止其後要執行的代碼,在panic所在函數F內如果存在要執行的defer函數列表,按照defer的逆序執行
3、返回函數F的調用者G,在G中,調用函數F語句之後的代碼不會執行,假如函數G中存在要執行的defer函數列表,按照defer的逆序執行
4、直到goroutine整個退出,並報告錯誤

recover:
1、內建函數
2、用來控制一個goroutine的panicking行爲,捕獲panic,從而影響應用的行爲
3、一般的調用建議
a). 在defer函數中,通過recever來終止一個gojroutine的panicking過程,從而恢復正常代碼的執行
b). 可以獲取通過panic傳遞的error

發佈了114 篇原創文章 · 獲贊 52 · 訪問量 28萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章