上一期我們講了 Go 中的錯誤處理,,這一期講講 Go 中的 異常 處理。錯誤和異常是兩個不同的概念,,非常容易混淆,。錯誤指的是可能出現(xiàn)問題的地方出現(xiàn)了問題;而異常指的是不應該出現(xiàn)問題的地方出現(xiàn)了問題,。
在有些情況,,當程序發(fā)生異常時,無法繼續(xù)運行,。在這種情況下,,我們會使用 panic
來終止程序。當函數(shù)發(fā)生 panic
時,,它會終止運行,,在執(zhí)行完所有的延遲函數(shù)后,程序控制返回到該函數(shù)的調(diào)用方,。這樣的過程會一直持續(xù)下去,,直到當前協(xié)程的所有函數(shù)都返回退出,然后程序會打印出 panic
信息,,接著打印出堆棧跟蹤,,最后程序終止。
我們應該盡可能地使用錯誤,,而不是使用 panic
和 recover
,。只有當程序不能繼續(xù)運行的時候,才應該使用 panic
和 recover
機制,。
panic
有兩個合理的用例:
- 發(fā)生了一個不能恢復的錯誤,,此時程序不能繼續(xù)運行。一個例子就是 web 服務器無法綁定所要求的端口,。在這種情況下,,就應該使用
panic
,因為如果不能綁定端口,,啥也做不了,。 - 發(fā)生了一個編程上的錯誤。假如我們有一個接收指針參數(shù)的方法,,而其他人使用
nil
作為參數(shù)調(diào)用了它,。在這種情況下,,我們可以使用 panic
,因為這是一個編程錯誤:用 nil
參數(shù)調(diào)用了一個只能接收合法指針的方法,。
下面是內(nèi)建函數(shù) panic
的簽名:
func panic(v interface{})
當程序終止時,,會打印傳入 panic
的參數(shù)。
package main
func main() {
panic("runtime error")
}
運行上面的程序,,會打印出傳入 panic
函數(shù)的信息,,并打印出堆棧跟蹤:
panic: runtime error
goroutine 1 [running]:
main.main()
(...)/main.go:4 +0x27
上面已經(jīng)提到了,當函數(shù)發(fā)生 panic
時,,它會終止運行,,在執(zhí)行完所有的延遲函數(shù)后,程序控制返回到該函數(shù)的調(diào)用方,。這樣的過程會一直持續(xù)下去,,直到當前協(xié)程的所有函數(shù)都返回退出,然后程序會打印出 panic
信息,,接著打印出堆棧跟蹤,,最后程序終止。下面通過一個簡單的例子看看是不是這樣:
package main
import "fmt"
func myTest() {
defer fmt.Println("defer in myTest")
panic("runtime error")
}
func main() {
defer fmt.Println("defer in main")
myTest()
}
運行該程序后輸出如下:
defer in myTest
defer in main
panic: runtime error
goroutine 1 [running]:
main.myTest()
(...)/main.go:7 +0x73
main.main()
(...)/main.go:11 +0x70
recover
是一個內(nèi)建函數(shù),,用于重新獲得 panic
協(xié)程的控制,。下面是內(nèi)建函數(shù) recover
的簽名:
func recover() interface{}
recover
必須在 defer
函數(shù)中才能生效,在其他作用域下,,它是不工作的,。在延遲函數(shù)內(nèi)調(diào)用 recover
,可以取到 panic
的錯誤信息,,并且停止 panic
續(xù)發(fā)事件,,程序運行恢復正常。下面是網(wǎng)上找的一個例子:
package main
import "fmt"
func myTest(x int) {
defer func() {
// recover() 可以將捕獲到的 panic 信息打印
if err := recover(); err != nil {
fmt.Println(err)
}
}()
var array [10]int
array[x] = 1
}
func main() {
// 故意制造數(shù)組越界 觸發(fā) panic
myTest(20)
// 如果能執(zhí)行到這句 說明 panic 被捕獲了
// 后續(xù)的程序能繼續(xù)運行
fmt.Println("everything is ok")
}
雖然該程序觸發(fā)了 panic
,,但由于我們使用了 recover()
捕獲了 panic
異常,,并輸出 panic
信息,即使 panic
會導致整個程序退出,,但在退出前,,有 defer
延遲函數(shù),還是得執(zhí)行完 defer
,。然后程序還會繼續(xù)執(zhí)行下去:
runtime error: index out of range [20] with length 10
everything is ok
這里要注意一點,,只有在相同的協(xié)程中調(diào)用 recover
才管用, recover
不能恢復一個不同協(xié)程的 panic
,。
package main
import (
"fmt"
"time"
)
func main() {
// 這個 defer 并不會執(zhí)行
defer fmt.Println("in main")
go func() {
defer println("in goroutine")
panic("")
}()
time.Sleep(time.Second)
}
運行后輸出如下:
in goroutine
panic:
goroutine 19 [running]:
main.main.func1()
(...)/main.go:13 +0x7b
created by main.main
(...)/main.go:11 +0x79
參考文獻:
[1] Alan A. A. Donovan; Brian W. Kernighan, Go 程序設計語言, Translated by 李道兵, 高博, 龐向才, 金鑫鑫 and 林齊斌, 機械工業(yè)出版社, 2017.