溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Go語言中panic函數(shù)、recover函數(shù)以及defer語句怎么用

發(fā)布時間:2021-10-21 14:06:19 來源:億速云 閱讀:168 作者:小新 欄目:編程語言

這篇文章主要介紹了Go語言中panic函數(shù)、recover函數(shù)以及defer語句怎么用,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。

panic

panic,Go語言的另外一種錯誤處理方式。嚴格來講,它處理的不是錯誤,而是異常,并且是一種在我們意料之外的程序異常。

panic詳情

要分析panic詳情,首先來生成一個panic。比如在一個切片里,它的長度是5,但是要通過索引5訪問其中的元素,這樣的訪問是不正確的。比如下面這樣:

func main() {
    l := []int{1, 2, 3, 4, 5}
    _ = l[5]
}

程序在運行時,會在執(zhí)行到這行代碼的時候拋出panic,提示用戶索引越界了。這不僅僅是個提示。當panic被拋出后,如果沒有在程序里添加任何保護措施的話,程序(或者說代表它的那個進程)會在打印出pinic的詳細情況之后終止運行。下面是panic的詳情:

panic: runtime error: index out of range

goroutine 1 [running]:
main.main()
        H:/Go/src/Go36/article21/example01/main.go:5 +0x3d
exit status 2

先看第一行的內容,其中的“runtime error”表示,這是一個runtime代碼包中拋出的panic。在這個panic中,包含一個runtime.Error接口類型的值。runtime.Error接口內嵌了error接口并做了一點擴展,runtime包中有不少它的實現(xiàn)類型。實際上,在"panic:"右邊的內容,就是這個panic包含的runtime.Error類型值的字符串表示形式。  
此外,panic詳情中一本還會包含與引發(fā)原因有關的goroutine的代碼執(zhí)行信息。這里的“goroutine 1 [running]:”表示是一個ID為1的goroutine在這個panic被引發(fā)的時候正在運行。  
再看下一行,“main.main()”表明了這個goroutine包裝的go函數(shù)就是命令源碼文件里的main函數(shù)。再往下一行,指出了這個源碼文件的絕對路徑,以及代碼在文件中所處的行。這一行的最后的+0x3d代表的是:此行代碼相對于其所屬函數(shù)的入口程序計數(shù)偏移量。不過,一般這個對我們沒什么用。  
最后的“exit status 2”,是這個程序退出的狀態(tài)碼。在大多數(shù)操作系統(tǒng)中,只要退出狀態(tài)碼不是0,就是非正常結束。在Go語言中,因panic導致的程序結束運行的退出狀態(tài)碼一般都會是2。

從panic被引發(fā)到程序終止運行的過程

先說一個大致的過程:當引發(fā)了一個panic。  
這時,初始的panic詳情會被建立起來,并且該程序的控制權會立即從此代碼行轉移至調用起所屬函數(shù)的那行代碼上,也就是調用棧中的上一級。這樣就意味著,此行代碼所屬的函數(shù)的執(zhí)行立即終止。  
緊接著,控制權并不會停在當前位置,它又會立即轉移至再上一級的調用代碼處??刂茩嗳绱艘患壱患壍匮刂{用棧的方向轉播至頂端,就是我們編寫的最外層的函數(shù)那里。這個最外層的函數(shù)就是go函數(shù),對于主goroutine來說就是main函數(shù)。但是控制權到這還不會停止轉移,而是被Go言語運行時的系統(tǒng)回收。  
隨后,程序崩潰并終止運行,運行這次程序的進程也會隨之死亡并消失。而在這個控制權傳播的過程中,panic詳情會被逐漸地積累和完善,并會在程序終止之前被打印出來。  
這里再補充一下,函數(shù)引發(fā)panic與函數(shù)返回錯誤值的意義是完全不同的  
當函數(shù)返回一個非nil的錯誤值時,函數(shù)的調用方有權選擇不處理,并且不處理的后果往往是不致命的。  
當一個panic發(fā)生時,如果不施加任何保護措施,那么導致的后果就是程序崩潰,這顯然是致命的。  
下面的例子清楚地展示了上面描述的控制權一級一級向上傳播的過程:

package main

import "fmt"

func main() {
    fmt.Println("main Start")
    caller1()
    fmt.Println("main End")
}

func caller1() {
    fmt.Println("caller1 Start")
    caller2()
    fmt.Println("caller1 End")
}

func caller2() {
    fmt.Println("caller2 Start")
    l := []int{1, 2, 3, 4, 5}
    _ = l[5]
    fmt.Println("caller2 End")
}

這里,panic詳情會在控制權傳播的過程中,被逐漸地積累和完善。并且,控制權會一級一級地沿著調用棧的反方向傳播至頂端。因此,針對某個goroutine的代碼執(zhí)行信息中,調用棧底端的信息會先出現(xiàn),然后是上一級調用的信息。以此類推,最后才是此調用棧頂端的信息。  
所以是,main函數(shù)調用了caller1函數(shù),而caller1函數(shù)又調用了caller2函數(shù)。那么caller2函數(shù)中代碼執(zhí)行的信息會先出現(xiàn),然后是caller1函數(shù)中代碼的執(zhí)行的信息,最后才是main函數(shù)的信息:

PS H:\Go\src\Go36\article21\example02> go run main.go
main Start
caller1 Start
caller2 Start
panic: runtime error: index out of range

goroutine 1 [running]:
main.caller2()
        H:/Go/src/Go36/article21/example02/main.go:20 +0xa2
main.caller1()
        H:/Go/src/Go36/article21/example02/main.go:13 +0x77
main.main()
        H:/Go/src/Go36/article21/example02/main.go:7 +0x77
exit status 2
PS H:\Go\src\Go36\article21\example02>

到這里,應該已經對panic被引發(fā)后的程序終止的過程有一定的了解了。深入了解這個過程以及正確的解讀panic詳情是一項必備技能。這在調試Go程序或為Go程序排查錯誤的時候非常有用。

panic函數(shù)、recover函數(shù)以及defer語句

如果一個panic是我們在無意間引發(fā)的,那么其中的值只能由Go語言運行時系統(tǒng)給定。但是,當我們使用panic函數(shù)有意的引發(fā)一個panic的時候,就可以自行指定其包含的值。

panic函數(shù)

在調用一個panic函數(shù)時,把某個值作為參數(shù)傳遞給函數(shù)就是可以了。panic函數(shù)只有一個參數(shù),并且類型是空接口,所以從語法上講,它可以接受任何類型的值。一旦程序異常了,就一定會把異常的相關信息記錄下來,所以就會需用輸出這個參數(shù)的字符串表示形式。雖然fmt.Sprintf和fmt.Fprintf這類可以格式化并輸出參數(shù)的函數(shù)也符合要求。不過,在功能上推薦使用自定義的Error方法或者String方法。因此,為部不同的數(shù)據(jù)類型分別編寫這兩種方法是首選。這樣,在程序崩潰的時候,panic包含的拿著值的字符串表示形式就會被打印出來:

package main

import (
    "fmt"
    // "errors"
)

func caller() {
    fmt.Println("caller Start")
    // panic(errors.New("Something Wrong"))  // 正例
    panic(fmt.Errorf("Something Wrong %s", "2"))  // 正例
    // panic(fmt.Println)  // 反例
}

func main() {
    fmt.Println("main Start")
    caller()
    fmt.Println("main End")
}

recover函數(shù)

可以施加應對panic的保護措施,避免程序崩潰。Go語言的內建函數(shù)recover專門用于恢復panic。recover無需任何參數(shù),并且會返回一個空接口類型的值。這個返回值,就是panic傳入的參數(shù)的副本。  
下面先看一個錯誤的用法:

func main() {
    fmt.Println("main Start")
    panic(errors.New("Something Wrong"))
    p := reover()
    fmt.Println(p)
    fmt.Pringln("main End")
}

這里,引發(fā)panic之后,想緊接著調用recover函數(shù)。但是,函數(shù)的執(zhí)行會在panic這行就終止了,這個recover函數(shù)的調用根本就沒有機會執(zhí)行。要想正確的調用recover函數(shù),需要用到defer語句。下面是修正過的代碼:

package main

import (
    "fmt"
    "errors"
)

func main() {
    fmt.Println("main Start")
    defer func() {
        fmt.Println("defer Start")
        if p := recover(); p != nil {
            fmt.Printf("Panic: %s\n", p)
        }
        fmt.Println("defer End")
    }()
    panic(errors.New("Something Wrong"))
    fmt.Println("main End")
}

在這個main函數(shù)中,先編寫了一條defer語句,并且再defer函數(shù)中調用了recover函數(shù)。僅當調用的結果不為nil時,也就是panic確實已經發(fā)生時,才會打印panic的內容。這里要盡量把defer語句寫在函數(shù)體的開始處,因為引發(fā)panic語句之后的所有語句,都不會有任何執(zhí)行的機會。

defer語句

defer語句就是被用來延遲執(zhí)行代碼的。延遲到該語句所在的函數(shù)即將執(zhí)行結束的那一刻,無論結束執(zhí)行的原因是什么(包括panic)。  
與別的go語句類型,一個defer語句有一個defer關鍵字和一個調用表達式組成。這里存在一些限制,有一些調用表達式是不能出現(xiàn)在這里的:針對Go語句內奸函數(shù)的調用表達式,以及針對unsafe包中的函數(shù)的調用表達式。在這里被調用函數(shù)可以是有名稱的,也可以是內名的??梢赃@這里的函數(shù)叫做defer函數(shù)或者延遲函數(shù)。注意,被延遲執(zhí)行的是defer函數(shù),而不是defer語句。  
defer執(zhí)行順序  
如果一個函數(shù)中有多條defer語句的情況,那么defer函數(shù)的調用的執(zhí)行順序與它們所屬的defer語句的執(zhí)行順序完全相反。當一個函數(shù)即將結束執(zhí)行時,其中寫在最下邊的defer函數(shù)調用會最新執(zhí)行,最上表的defer函數(shù)調用會最后一個執(zhí)行。  
在defer語句每次執(zhí)行的時候,Go語言會把它所攜帶的defer函數(shù)及其參數(shù)值另行存儲到一個隊列中。這個隊列與該defer語句所屬的函數(shù)是對應的,并且它是先進后出(FILO)的,想到與一個棧。在需要執(zhí)行某個函數(shù)的defer函數(shù)調用的時候,Go語言會先拿到對應的隊列,然后從該隊列中一個一個的取出defer函數(shù)及其參數(shù)值并逐個執(zhí)行調用。這就是實現(xiàn)這個執(zhí)行順序的原因了。  
下面是一個簡單的示例,展示了defer的調用順序:

package main

import "fmt"

func main() {
    defer fmt.Println("first defer")
    for i := 0; i < 3; i++ {
        defer fmt.Printf("defer in for [%d]\n", i)
    }
    defer fmt.Println("last defer")
}

感謝你能夠認真閱讀完這篇文章,希望小編分享的“Go語言中panic函數(shù)、recover函數(shù)以及defer語句怎么用”這篇文章對大家有幫助,同時也希望大家多多支持億速云,關注億速云行業(yè)資訊頻道,更多相關知識等著你來學習!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經查實,將立刻刪除涉嫌侵權內容。

AI