再读Golang中的异常处理

2,116 阅读3分钟

一起重温Golang中的异常处理啊😸

  • 1.Golang语言中没有其他语言中的try...catch...语句来捕获异常和异常恢复
  • 2.在Golang中我们通常会使用panic关键字来抛出异常,在defer中使用recover来捕获异常进行具体逻辑处理
  • 3.Golang中我们通常会在函数或方法中返回error结构对象来判断是否有异常出现

注意事项

  • 1.利用recoverpanic指令,defer必须放在panic之前定义(panic会终止其后要执行的代码).
  • 2.recover只有在defer调用的函数中才有效,否则recover无法捕获到panic.
  • 3.recover处理异常后,业务逻辑会跑到defer之后的处理片段中
  • 4.多个defer会形成defer栈
  • 5.panic会等到整个goroutine退出才会报告错误

常规使用

  1. panic以及recover参数类型为空接口(可存储任何类型对象)interface{}
/*
func panic(v interface{})
func recover() interface{}
执行顺序:panic()->带recover的defer
输出结果:
oh my god!panic.
解释:
defer中的recover成功捕获到了panic的异常
*/

package main
import (
    "fmt"
)
func main() {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println(err)
        }
    }()
    panic("oh my god!panic.")
}

  1. 延迟调用中引发的错误,可被后续延迟调用捕获(仅最后一个错误被捕获)
/*
执行顺序:panic()->带panic的defer匿名函数->带recover()的defer匿名函数
输出结果:
catch the panic
解释:
defer中的recover仅能捕获最后一个错误
package main
import (
    "fmt"
)
func main() {
    defer func() {
        if err := recover();err != nil {
            fmt.Println("catch the panic")
        }
    }()
    defer func() {
        panic("oh my god! panic.")
    }()

    panic("something panic!")

}

  1. 捕获函数recover()只有在defer调用内直接调用才会终止,否则返回nil
/*
代码执行顺序:panic->在匿名函数中嵌套recover的defer函数->带fmt的defer->带recover的defer->在匿名函数中调用recover的defer
输出结果:
defer inner
<nil>
defer recover panic error
解释: 多个defer之间形成defer栈,最底部的defer优先执行;第三个defer打印了recover()的零值`nil`,仅有第一个defer成功捕获了最底部的panic("panic error")
*/
package main
import "fmt"
func main() {
    defer func() {
        fmt.Println("defer recover",recover())
    }()
    defer recover()
    defer fmt.Println(recover())
    defer func() {
        func(){
            fmt.Println("defer inner")
            recover()
        }()
    }()
    panic("panic error")
}

  1. 将代码块放置在匿名函数中可实现在函数逻辑中进行异常恢复,而不影响主函数
/*
代码执行顺序:匿名函数中的panic语句->匿名函数中i自加运算->匿名函数中的fmt->匿名函数中的defer->主函数中的fmt
输出结果:
i is: 2
解释:panic会终止其之后的执行,因此优先执行匿名函数中的panic之后便被defer中的recover捕获,将i赋值为2,其后匿名函数退出开始继续执行主函数中的fmt.Println语句
*/
package main
import "fmt"
func main() {
    test()
}
func test() {
    var i int
    func() {
        defer func(){
            if err := recover();err != nil {
                i = 2
            }
        }()
        panic("something panic!")
        i += 8
        fmt.Println("no panic, i is:",i)
    }()
    fmt.Println("i is:",i)
}

  1. goroutine中的recover

注意:如果一个没有recovergoroutine发生了panic,那么整个进程都会挂掉

/*
sync.WaitGroup用来等待一组goroutine的结束,Add方法用来设置等待的goroutine数量,Done方法表示一个goroutine运行结束,使用Wait方法将全部的goroutine阻塞住,直到全部goroutine执行完毕

代码执行顺序:goroutine中的逻辑->wg.Wait()->fmt.Println
输出结果:
panic recover assignment to entry in nil map
donw
解释:
在goroutine中我们声明了一个info的map[string]string类型,我们都知道在map,slice,channel都是引用类型,需要使用make函数进行初始化操作之后进行赋值。而这里直接使用info["name"] = "BGBiao"进行赋值导致panic,fmt.Println函数就会被终止执行,从而执行带recover的defer,之后执行带wg.Done()的defer并退出goroutine执行主程序逻辑
*/
package main
import (
    "fmt"
    "sync"
)
func main() {
    var wg sync.WaitGroup
    wg.Add(4)
    go func() {
        defer wg.Done()
        defer func() {
            if err := recover();err != nil {
                fmt.Println("panic recover",err)
            }
        }()
        var info map[string]string
        info["name"] = "BGBiao"
        fmt.Println(info)
    }()
    wg.Wait()
    fmt.Println("done")
}

欢迎关注我的公众号: BGBiao,一起进步~