go defer,panic,recover详解 go 的异常处理
golang中defer,panic,recover是很常用的三個特性,三者一起使用可以充當其他語言中try…catch…的角色,而defer本身又像其他語言的析構函數
defer
defer后邊會接一個函數,但該函數不會立刻被執行,而是等到包含它的程序返回時(包含它的函數執行了return語句、運行到函數結尾自動返回、對應的goroutine panic)defer函數才會被執行。通常用于資源釋放、打印日志、異常捕獲等
func main() {f, err := os.Open(filename)if err != nil {return err}/*** 這里defer要寫在err判斷的后邊而不是os.Open后邊* 如果資源沒有獲取成功,就沒有必要對資源執行釋放操作* 如果err不為nil而執行資源執行釋放操作,有可能導致panic*/defer f.Close() }如果有多個defer函數,調用順序類似于棧,越后面的defer函數越先被執行(后進先出)
func main() {defer fmt.Println(1)defer fmt.Println(2)defer fmt.Println(3)defer fmt.Println(4) }結果:
4 3 2 1如果包含defer函數的外層函數有返回值,而defer函數中可能會修改該返回值,最終導致外層函數實際的返回值可能與你想象的不一致,這里很容易踩坑,來幾個🌰
例1
func f() (result int) {defer func() {result++}()return 0 }例2
func f() (r int) {t := 5defer func() {t = t + 5}()return t }例3
func f() (r int) {defer func(r int) {r = r + 5}(r)return 1 }請先不要向下看,在心里跑一遍上邊三個例子的結果,然后去驗證
可能你會認為:例1的結果是0,例2的結果是10,例3的結果是6,那么很遺憾的告訴你,這三個結果都錯了
為什么呢,最重要的一點就是要明白,return xxx這一條語句并不是一條原子指令
含有defer函數的外層函數,返回的過程是這樣的:先給返回值賦值,然后調用defer函數,最后才是返回到更上一級調用函數中,可以用一個簡單的轉換規則將return xxx改寫成
例1可以改寫成這樣
func f() (result int) {result = 0//在return之前,執行defer函數func() {result++}()return }所以例1的返回值是1
例2可以改寫成這樣
func f() (r int) {t := 5//賦值r = t//在return之前,執行defer函數,defer函數沒有對返回值r進行修改,只是修改了變量tfunc() {t = t + 5}return }所以例2的結果是5
例3可以改寫成這樣
func f() (r int) {//給返回值賦值r = 1/*** 這里修改的r是函數形參的值,是外部傳進來的* func(r int){}里邊r的作用域只該func內,修改該值不會改變func外的r值*/func(r int) {r = r + 5}(r)return }所以例3的結果是1
defer函數的參數值,是在申明defer時確定下來的
在defer函數申明時,對外部變量的引用是有兩種方式:作為函數參數和作為閉包引用
作為函數參數,在defer申明時就把值傳遞給defer,并將值緩存起來,調用defer的時候使用緩存的值進行計算(如上邊的例3)
而作為閉包引用,在defer函數執行時根據整個上下文確定當前的值
看個🌰
結果:
c: 1 b: 0 a: 0by smoke_zl
panic和recover
func panic(v interface{})中英文說明
The panic built-in function stops normal execution of the current goroutine.When a function F calls panic, normal execution of F stops immediately.Any functions whose execution was deferred by F are run in the usual way, and then F returns to its caller. To the caller G, the invocation of F then behaves like a call to panic,terminating G's execution and running any deferred functions.This continues until all functions in the executing goroutine have stopped,in reverse order. At that point, the program is terminated and the error condition is reported,including the value of the argument to panic. This termination sequence is called panicking and can be controlled by the built-in function recover.
panic內置函數停止當前goroutine的正常執行,當函數F調用panic時,函數F的正常執行被立即停止,然后運行所有在F函數中的defer函數,然后F返回到調用他的函數對于調用者G,F函數的行為就像panic一樣,終止G的執行并運行G中所defer函數,此過程會一直繼續執行到goroutine所有的函數。panic可以通過內置的recover來捕獲。
中英文說明
The recover built-in function allows a program to manage behavior of a panicking goroutine. Executing a call to recover inside a deferred function (but not any function called by it) stops the panicking sequence by restoring normal execution and retrieves the error value passed to the call of panic. If recover is called outside the deferred function it will not stop a panicking sequence. In this case, or when the goroutine is not panicking, or if the argument supplied to panic was nil, recover returns nil. Thus the return value from recover reports whether the goroutine is panicking.
recover內置函數用來管理含有panic行為的goroutine,recover運行在defer函數中,獲取panic拋出的錯誤值,并將程序恢復成正常執行的狀態。如果在defer函數之外調用recover,那么recover不會停止并且捕獲panic錯誤如果goroutine中沒有panic或者捕獲的panic的值為nil,recover的返回值也是nil。由此可見,recover的返回值表示當前goroutine是否有panic行為
這里有幾個需要注意的問題,通過🌰表現
1、defer 表達式的函數如果定義在 panic 后面,該函數在 panic 后就無法被執行到
在defer前panic
func main() {panic("a")defer func() {fmt.Println("b")}() }結果,b沒有被打印出來:
panic: agoroutine 1 [running]: main.main()/xxxxx/src/xxx.go:50 +0x39 exit status 2而在defer后panic
func main() {defer func() {fmt.Println("b")}()panic("a") }結果,b被正常打印:
b panic: agoroutine 1 [running]: main.main()/xxxxx/src/xxx.go:50 +0x39 exit status 22、F中出現panic時,F函數會立刻終止,不會執行F函數內panic后面的內容,但不會立刻return,而是調用F的defer,如果F的defer中有recover捕獲,則F在執行完defer后正常返回,調用函數F的函數G繼續正常執行
func G() {defer func() {fmt.Println("c")}()F()fmt.Println("繼續執行") }func F() {defer func() {if err := recover(); err != nil {fmt.Println("捕獲異常:", err)}fmt.Println("b")}()panic("a") }結果:
捕獲異常: a b 繼續執行 c3、如果F的defer中無recover捕獲,則將panic拋到G中,G函數會立刻終止,不會執行G函數內后面的內容,但不會立刻return,而調用G的defer...以此類推
func G() {defer func() {if err := recover(); err != nil {fmt.Println("捕獲異常:", err)}fmt.Println("c")}()F()fmt.Println("繼續執行") }func F() {defer func() {fmt.Println("b")}()panic("a") }結果:
b 捕獲異常: a c4、如果一直沒有recover,拋出的panic到當前goroutine最上層函數時,程序直接異常終止
func G() {defer func() {fmt.Println("c")}()F()fmt.Println("繼續執行") }func F() {defer func() {fmt.Println("b")}()panic("a") }結果:
b c panic: agoroutine 1 [running]: main.F()/xxxxx/src/xxx.go:61 +0x55 main.G()/xxxxx/src/xxx.go:53 +0x42 exit status 25、recover都是在當前的goroutine里進行捕獲的,這就是說,對于創建goroutine的外層函數,如果goroutine內部發生panic并且內部沒有用recover,外層函數是無法用recover來捕獲的,這樣會造成程序崩潰
func G() {defer func() {//goroutine外進行recoverif err := recover(); err != nil {fmt.Println("捕獲異常:", err)}fmt.Println("c")}()//創建goroutine調用F函數go F()time.Sleep(time.Second) }func F() {defer func() {fmt.Println("b")}()//goroutine內部拋出panicpanic("a") }結果:
b panic: agoroutine 5 [running]: main.F()/xxxxx/src/xxx.go:67 +0x55 created by main.main/xxxxx/src/xxx.go:58 +0x51 exit status 26、recover返回的是interface{}類型而不是go中的 error 類型,如果外層函數需要調用err.Error(),會編譯錯誤,也可能會在執行時panic
func main() {defer func() {if err := recover(); err != nil {fmt.Println("捕獲異常:", err.Error())}}()panic("a") }編譯錯誤,結果:
err.Error undefined (type interface {} is interface with no methods)
func main() {defer func() {if err := recover(); err != nil {fmt.Println("捕獲異常:", fmt.Errorf("%v", err).Error())}}()panic("a") }結果:
捕獲異常: a參考:
https: //golang.org/pkg/builtin/#recover
https: //www.w3cschool.cn/go_internals/go_internals-drq7282o.html
作者:smoke_zl
鏈接:https://www.jianshu.com/p/63e3d57f285f
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
總結
以上是生活随笔為你收集整理的go defer,panic,recover详解 go 的异常处理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 低代码指南100解决方案:47深度解读丰
- 下一篇: 银行系统软件测试的目的,商业银行软件缺陷