golang没有像其他语言那样提供try{}catch(){}方式去处理异常,但是可以通过panic和recover捕获处理异常。
在程序运行中,无论是显式或者隐式触发了panic,它会立即停止当前函数的执行,并沿着函数调用栈向上传播,直到被捕获或达到顶层的main函数,导致程序终止。
如下面的案例,第一个testFunc调用会执行,中间调用了panicFunc,触发了panic,下面的testFunc就不会被执行。
func testFunc() {
fmt.Println("this is test func")
}
func panicFunc() {
panic("this is panic")
}
func main() {
testFunc()
panicFunc()
testFunc()
}
这时,如下面案例,我们需要对testFunc进行recover捕获处理,再次编译运行,将不影响最后一个testFunc的执行。
func testFunc() {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()
panic("this is panic")
}
但是有时候需要给客户端返回这个异常,这种属于主动去调用panic,但是有时候系统触发的异常,想捕获返回给客户端怎么处理呢,比如两个数相除,除数不能是0,我们可以定义一个error的返回类型。判断err是否空,返回给客户端。
func testFn(n, m int) (cal int, err error) {
defer func() {
r := recover()
if r != nil {
//TODO可以执行一些监控接口,通知异常
err = fmt.Errorf("Panic error: %v", r)
}
}()
cal = n / m
return
}
func main() {
cal, err := testFn(5, 0)
fmt.Println(err)
fmt.Println(cal)
}
//输出
Panic error: runtime error: integer divide by zero
0
对于一些非系统的异常,可以通过自定义的错误信息抛出panic返回。例如做一些订单的校验业务
func checkName(name string) (err error) {
if name == "iamzcr" {
return nil
} else {
return errors.New("name error")
}
}
func checkOrder(orderNum string) (err error) {
if orderNum == "123456" {
return nil
} else {
return errors.New("order num error")
}
}
func check(orderData map[string]string) (err error) {
defer func() {
r := recover()
if r != nil {
//TODO可以执行一些监控接口,通知异常
err = fmt.Errorf("Panic error: %v", r)
}
}()
err = checkName(orderData["name"])
if err != nil {
panic(err)
}
err = checkOrder(orderData["order_num"])
if err != nil {
panic(err)
}
return
}
func main() {
var orderData = make(map[string]string)
orderData["name"] = "iamzc1r"
orderData["order_num"] = "123456"
err := check(orderData)
if err == nil {
fmt.Println("order check success")
} else {
fmt.Println("order check fail msg:", err)
}
}
如果在go协程里面,如果不处理可能潜在的panic,不仅是一个协程异常,其他主进程和其他协程也会退出,下面的程序,开辟了5个协程,处理的业务是两个数相除,如果在函数里面testGoFn不处理系统抛出的异常,会导致整个程序奔溃。
func testGoFn(n, m int) {
defer func() {
r := recover()
if r != nil {
//TODO可以执行一些监控接口,通知异常
fmt.Printf("Panic error: %v\n", r)
}
}()
fmt.Printf("cal: %v\n", n/m)
}
func main() {
fmt.Println("main start")
for i := 0; i < 5; i++ {
n := rand.Intn(3) // 生成0到2之间的随机整数
m := rand.Intn(3) // 生成0到2之间的随机整数
go testGoFn(n, m)
}
time.Sleep(2 * time.Second)
fmt.Println("main end")
}
小结
当程序发生panic时,它会立即停止当前函数的执行,并沿着函数调用栈向上传播,直到被捕获或达到顶层的main函数,导致程序终止。
如果在defer语句中调用了recover,并且在同一协程中发生了panic,那么recover将返回panic的值,程序将继续执行后续的语句,而不是终止程序。
发表评论