什么是 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")
}
如果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")
}
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í)行
但是你發(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")
}