panic/recover工作原理
在 Golang 裡面沒有大家所熟悉的 try-cache 異常機制、而是用 panic/recover 代替 異常 throw / catch 和 panic / recover 是有些類似的,比如、recover 的作用是捕獲並返回 panic 提交的錯誤物件、這可以理解為通過呼叫 panic 丟擲一個值、該值可以通過呼叫 recover 函式進行捕獲。 主要的區別是,即使當前 goroutine 處於 panic 狀態,或當前 goroutine 中存在活動緊急情況,恢復呼叫仍可能無法檢索這些活動緊急情況丟擲的值。
For example:
package main
import (
"fmt"
)
func main() {
defer func() {
defer func() {
fmt.Println("6:", recover())
}()
}()
defer func() {
func() {
fmt.Println("5:", recover())
}()
}()
func() {
defer func() {
fmt.Println("1:", recover())
}()
}()
func() {
defer fmt.Println("2:", recover())
}()
func() {
fmt.Println("3:", recover())
}()
fmt.Println("4:", recover())
panic(1)
defer func() {
fmt.Println("0:", recover()) // never go here
}()
}
上述程式中的 7 個恢復呼叫都沒有恢復程式。 程式崩潰並列印出堆疊跟蹤:
$ go run example1.go 1: <nil> 2: <nil> 3: <nil> 4: <nil> 5: <nil> 6: <nil> panic: 1
goroutine 1 [running]: ... 顯然,第 0 個恢復呼叫是不可達的。 對於其他 recover,讓我們先檢查 Go 規範: 如果滿足以下任一條件,recover 的返回值為 nil:
- 1.panic 引數是 nil
- 2.當前 goroutine 沒有產生 panic
- 3.recover 不是由延遲函式直接呼叫。
讓我們忽略第一個條件。 第二個條件覆蓋第一/第二/第三和第四 recover 呼叫。 第三個覆蓋第 5 個 recover 呼叫。 然而,三個條件中沒有一個覆蓋第六個 recover 呼叫。 怎樣才能讓 recover 呼叫起作用?如下 :
import("fmt")
func main(){
defer func(){
fmt.Println(recover())// 1
}()
panic(1)
}
現在,panic 值被 recover 呼叫捕獲,並且程式不會崩潰。 那麼,使 recover 呼叫生效的主要規則是什麼? 首先,讓我們學習一些概念和說明。
概念:延遲函式呼叫概念:延遲函式呼叫
當函式被延遲呼叫時,或者函式的呼叫使用 defer 關鍵字作為字首,則呼叫被稱為延遲呼叫。
package main
func main(){
defer func(){// deferred function calling
func(){// not a deferred function calling
defer recover()// deferred function calling
}()
}()
func(){
// not a deferred function calling
defer func(){
/// deferred function calling
}()
}()
}
概念:函式呼叫級別和 Goroutine 執行級別
函式呼叫級別是指函式呼叫的深度,與主函式或者 goroutine 的入口函式相關。
package main
funcmain(){
// level 0
go func(){
// level 0
func(){
// level 1
}()
func(){
// level 1
func(){
// level 2
}()
}()
}()
func(){
// level 1
func(){
// level 2
go func(){
// level 0
}()
}()
go func(){
// level 0
}()
}()
}
goroutine 當前執行點的呼叫級別稱為 goroutine 的執行級別。
說明:panic 只能向上傳播
是的,panic 只能向上傳播,沿著函式呼叫堆疊反向。panic 從不通過深入到函式呼叫中傳播。
package main
import"fmt"
func main(){// calling level 0
defer func(){// calling level 1
fmt.Println("Now, the panic is still in calling level 0")
func(){// calling level 2
fmt.Println("Now, the panic is still in calling level 0")
func(){// calling level 3
fmt.Println("Now, the panic is still in calling level 0")
}()
}()
}()
defer fmt.Println("Now, the panic is in calling level 0")
func(){
// calling level 1
defer fmt.Println("Now, the panic is in calling level 1")
func(){// calling level 2
defer fmt.Println("Now, the panic is in calling level 2")
func(){// calling level 3
defer fmt.Println("Now, the panic is in calling level 3")
panic(1)
}()
}()
}()
}
The output: Now, the panic is in calling level 3
Now, the panic is in calling level 2
Now, the panic is in calling level 1
Now, the panic is in calling level 0
Now, the panic is still in calling level 0
Now, the panic is still in calling level 0
Now, the panic is still in calling level 0
panic: 1
goroutine 1 [running]:
...
概念:panic 級別
panic 級別意味著 panic 傳播到函式呼叫的哪一層級、因為 panic 只能向上傳遞、所以 panic 等級永遠不加增加、只會減小、在 goroutine 中,當前 panic 的水平永遠不會大於 goroutine 的執行水平。
說明:在同一層級上、新的 panics 會壓制原有 panics
Example:
package main
import"fmt"
func main(){
defer fmt.Println("program will not crash")
defer func(){
fmt.Println(recover())// 3
}()
defer fmt.Println("now, panic 3 suppresses panic 2")
defer panic(3)
defer fmt.Println("now, panic 2 suppresses panic 1")
defer panic(2)
panic(1)
}
Outputs:
now, panic 2 suppresses panic 1
now, panic 3 suppresses panic 2
3
program will not crash
在以上程式中、我們最終只能看到一個 panic 3、panic 3 被 recover 捕獲、因此程式不會崩潰 在一個 goroutine 中,在任何時候在相同的呼叫級別將有至多一個主動 panic。 特別是,當執行點執行在 goroutine 的呼叫級別 0 時,在 goroutine 中最多隻有一個活動的 panic。
說明:多個主動 panic 在一個 Goroutine 中的共存
Example:
package main
import"fmt"
func main(){// callnig level 0
defer fmt.Println("program will crash, for panic 3 is stll active")
defer func(){// calling level 1
defer func(){// calling level 2
fmt.Println(recover())// 6
}()// the level of panic 3 is 0.// the level of panic 6 is 1.
defer fmt.Println("now, there are two active panics: 3 and 6")
defer panic(6)// will suppress panic 5
defer panic(5)// will suppress panic 4
panic(4)// will not suppress panic 3, for they have differrent levels
// the level of panic 3 is 0.// the level of panic 4 is 1.
}()
defer fmt.Println("now, only panic 3 is active")
defer panic(3)// will suppress panic 2
defer panic(2)// will suppress panic 1
panic(1)
}
在該示例中,panic 6、兩個處於 active 狀態中的 panic 之一被 recover 捕獲。 但是其他 panic,panic 3,在主呼叫結束時仍然活動,所以程式將崩潰。 Outputs:
now, only panic 3 is active
now, there are two active
panics: 3 and 6
6
program will crash, for panic 3 is stll active
panic: 1
panic: 2
panic: 3
goroutine 1 [running]: ...
說明:低階的 panics 將會被首先捕獲
Example:
package main
import"fmt"
func main(){
defer func(){
defer func(){
fmt.Println("panic",recover(),"is recovered")// panic 2 is recovered
}()
defer fmt.Println("panic",recover(),"is recovered")// panic 1 is recovered
defer fmt.Println("now, two active panics coexist")
panic(2)
}()
panic(1)
}
Outputs:
now, two active panics coexist
panic 1 is recovered
panic 2 is recovered
那麼,什麼是使 recover 呼叫生效的主要規則是什麼? 規則很簡單
在一個 goroutine 中,如果 recover 呼叫的呼叫函式是 F 並且 F 呼叫的級別是 L,則為了使 recover 呼叫生效,F 呼叫必須是延遲呼叫,並且必須存在主動 panic 的水平為 L-1。 相對來說、這是一個比 go 規格更好的描述、 現在你可以回頁面檢查為什麼第一個例子中的第 6 個 recover 呼叫不會生效了
原文連結:http://www.tapirgames.com/blog/golang-panic-recover-mechanism
- 加微信實戰群請加微信(註明:實戰群):gocnio
相關文章
- 【譯】defer-panic-and-recover
- Go基礎系列:defer、panic和recoverGo
- Go 中的Defer,Panic 和 Recover 控制流Go
- 【Go進階—基礎特性】panic 和 recoverGo
- Golang 高效實踐之defer、panic、recover實踐Golang
- [譯] Part 32: 詳解Golang 中的Panic 和 RecoverGolang
- oracle recover 原理概括Oracle
- Golang錯誤處理函式defer、panic、recover、errors.New介紹Golang函式Error
- Go語言中用於錯誤處理的Defer、Panic和Recover - Sachin KarveGo
- 冷備手工完全恢復(recover database,recover tablespace,recover datafile)Database
- require工作原理UI
- Mybatis工作原理MyBatis
- Nginx工作原理Nginx
- Handler 工作原理
- rman工作原理
- mydumper工作原理
- pr工作原理
- go的 err!=nil 和 panic+recover 這兩種錯誤處理機制的關係和區別是什麼?Go
- cron with recover
- javascript引擎工作原理JavaScript
- ZStack基本工作原理
- Mirror 的工作原理
- Web Scraper工作原理Web
- HTTPS工作原理HTTP
- SpringMVC工作原理SpringMVC
- Spark的工作原理Spark
- Docker 工作原理分析Docker
- 【MySQL】Mydumper工作原理MySql
- View的工作原理View
- HashMap的工作原理HashMap
- DHCP的工作原理
- tcmalloc的工作原理
- 相機工作原理
- undo的工作原理
- MySQL:索引工作原理MySql索引
- ORACLE RAC工作原理Oracle
- FTP工作原理(轉)FTP
- pr工作原理文件