panic 和 recover

什么是 panic?

在 Go 語(yǔ)言中,程序中一般是使用錯(cuò)誤來(lái)處理異常情況。對(duì)于程序中出現(xiàn)的大部分異常情況,錯(cuò)誤就已經(jīng)夠用了。

但在有些情況,當(dāng)程序發(fā)生異常時(shí),無(wú)法繼續(xù)運(yùn)行。在這種情況下,我們會(huì)使用 panic 來(lái)終止程序。當(dāng)函數(shù)發(fā)生 panic 時(shí),它會(huì)終止運(yùn)行,在執(zhí)行完所有的延遲函數(shù)后,程序控制返回到該函數(shù)的調(diào)用方。這樣的過(guò)程會(huì)一直持續(xù)下去,直到當(dāng)前協(xié)程的所有函數(shù)都返回退出,然后程序會(huì)打印出 panic 信息,接著打印出堆棧跟蹤(Stack Trace),最后程序終止。在編寫(xiě)一個(gè)示例程序后,我們就能很好地理解這個(gè)概念了。

在本教程里,我們還會(huì)接著討論,當(dāng)程序發(fā)生 panic 時(shí),使用 recover 可以重新獲得對(duì)該程序的控制。

可以認(rèn)為 panic 和 recover 與其他語(yǔ)言中的 try-catch-finally 語(yǔ)句類(lèi)似,只不過(guò)一般我們很少使用 panic 和 recover。而當(dāng)我們使用了 panic 和 recover 時(shí),也會(huì)比 try-catch-finally 更加優(yōu)雅,代碼更加整潔。

什么時(shí)候應(yīng)該使用 panic?
需要注意的是,你應(yīng)該盡可能地使用錯(cuò)誤,而不是使用 panic 和 recover。只有當(dāng)程序不能繼續(xù)運(yùn)行的時(shí)候,才應(yīng)該使用 panic 和 recover 機(jī)制。

panic 有兩個(gè)合理的用例。

發(fā)生了一個(gè)不能恢復(fù)的錯(cuò)誤,此時(shí)程序不能繼續(xù)運(yùn)行。 一個(gè)例子就是 web 服務(wù)器無(wú)法綁定所要求的端口。在這種情況下,就應(yīng)該使用 panic,因?yàn)槿绻荒芙壎ǘ丝冢兑沧霾涣恕?/p>

發(fā)生了一個(gè)編程上的錯(cuò)誤。 假如我們有一個(gè)接收指針參數(shù)的方法,而其他人使用 nil 作為參數(shù)調(diào)用了它。在這種情況下,我們可以使用 panic,因?yàn)檫@是一個(gè)編程錯(cuò)誤:用 nil 參數(shù)調(diào)用了一個(gè)只能接收合法指針的方法。

這是一個(gè)內(nèi)建函數(shù),傳入數(shù)據(jù)即可

func panic(interface{})
import (  
    "fmt"
)

func fullName(firstName *string, lastName *string) {  
    if firstName == nil {
        panic("runtime error: first name cannot be nil")
    }
    if lastName == nil {
        panic("runtime error: last name cannot be nil")
    }
    fmt.Printf("%s %s\n", *firstName, *lastName)
    fmt.Println("returned normally from fullName")
}

func main() {  
    firstName := "Elon"
    fullName(&firstName, nil)
    fmt.Println("returned normally from main")
}

defer 遇到 panic

我們重新總結(jié)一下 panic 做了什么。當(dāng)函數(shù)發(fā)生 panic 時(shí),它會(huì)終止運(yùn)行,在執(zhí)行完所有的延遲函數(shù)后,程序控制返回到該函數(shù)的調(diào)用方。這樣的過(guò)程會(huì)一直持續(xù)下去,直到當(dāng)前協(xié)程的所有函數(shù)都返回退出,然后程序會(huì)打印出 panic 信息,接著打印出堆棧跟蹤,最后程序終止

panic 其實(shí)是一個(gè)終止函數(shù)棧執(zhí)行的過(guò)程,但是在函數(shù)退出前都會(huì)執(zhí)行defer里面的函數(shù),知道所有的函數(shù)都退出后,才會(huì)執(zhí)行panic

package main

import (
    "fmt"
)

func fullName(firstName *string, lastName *string) {
    defer fmt.Println("deferred call in fullName")

    if firstName == nil {
        panic("runtime error: first name cannot be nil")
    }
    if lastName == nil {
        panic("runtime error: last name cannot be nil")
    }
    fmt.Printf("%s %s\n", *firstName, *lastName)
    fmt.Println("returned normally from fullName")
}

func main() {
    defer fmt.Println("deferred call in main")
    firstName := "Elon"
    fullName(&firstName, nil)
    fmt.Println("returned normally from main")
}
image.png

如果defer中也有panic 那么會(huì)依次按照發(fā)生panic的順序執(zhí)行


recover

func recover() interface{}

主要在defer 中才有效,這個(gè)一定要記住

package main

import (
    "fmt"
)

func fullName(firstName *string, lastName *string) {
    defer recover()

    if firstName == nil {
        panic("runtime error: first name cannot be nil")
    }
    if lastName == nil {
        //panic("runtime error: last name cannot be nil")
    }
    //fmt.Printf("%s %s\n", *firstName, *lastName)
    fmt.Println("returned normally from fullName")
}

func main() {
    defer fmt.Println("deferred call in main")
    firstName := "Elon"
    fullName(&firstName, nil)
    fmt.Println("returned normally from main")
}
image.png

panic,recover 和 Go 協(xié)程

recover 只能回復(fù)同一個(gè)協(xié)程中的panic

package main

import (
    "fmt"
)

func fullName(firstName *string, lastName *string) {
    defer recover() // 這樣的寫(xiě)法不能恢復(fù)panic 
    if firstName == nil {
       panic("runtime error: first name cannot be nil")
    }
    if lastName == nil {
       panic("runtime error: last name cannot be nil")
    }
    fmt.Printf("%s %s\n", *firstName, *lastName)
    fmt.Println("returned normally from fullName")
}
func main() {
    defer fmt.Println("deferred call in main")
    firstName := "Elon"
    fullName(&firstName, nil)
    fmt.Println("returned normally from main")
}

依然報(bào)錯(cuò)
請(qǐng)使用下面的方式恢復(fù)

package main

import (
    "fmt"
)

func fullName(firstName *string, lastName *string) {
    defer r()
    if firstName == nil {
       panic("runtime error: first name cannot be nil")
    }
    if lastName == nil {
       panic("runtime error: last name cannot be nil")
    }
    fmt.Printf("%s %s\n", *firstName, *lastName)
    fmt.Println("returned normally from fullName")
}
func r(){
    recover()
}

func main() {
    defer fmt.Println("deferred call in main")
    firstName := "Elon"
    fullName(&firstName, nil)
    fmt.Println("returned normally from main")
}

只要程序發(fā)生panic,就會(huì)到調(diào)用延時(shí)函數(shù) defer r() r函數(shù)會(huì)恢復(fù)這個(gè)panic 程序會(huì)回到主函數(shù)繼續(xù)執(zhí)行

image.png

但是你發(fā)現(xiàn)沒(méi)有錯(cuò)誤日志輸出了,如果我們希望將panic的錯(cuò)誤棧數(shù)據(jù)顯示出來(lái)怎么辦呢?

package main
import (
    "fmt"
    "runtime/debug"
)
func fullName(firstName *string, lastName *string) {
    defer r()

    if firstName == nil {
       panic("runtime error: first name cannot be nil")
    }
    if lastName == nil {
       panic("runtime error: last name cannot be nil")
    }
    fmt.Printf("%s %s\n", *firstName, *lastName)
    fmt.Println("returned normally from fullName")
}
func r(){
    if s := recover();s!=nil{
        fmt.Println(s)
        // 打印堆棧跟蹤
        debug.PrintStack()
    }
}

func main() {
    defer fmt.Println("deferred call in main")
    firstName := "Elon"
    fullName(&firstName, nil)
    fmt.Println("returned normally from main")
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容