Golang 中没有内置的类似 (try…catch…finally) 的异常处理手段,但是有其他的替代方案。
文章目录
关键的内置函数和关键字
panic
Golang 的内置函数,当运行到这个函数时,系统会抛出运行时异常,如果没有逻辑兜底,那么系统代码将不再向下继续执行。
如下代码将打印出堆栈信息及代码出错的位置
func panicCreater() {
is := 0
fmt.Println(10 / is)
}
func main() {
fmt.Println("1")
fmt.Println("2")
is := 0
fmt.Println(10 / is)
fmt.Println("3")
}
---------------------
1
2
panic: runtime error: integer divide by zero
goroutine 1 [running]:
main.panicCreater()
e:/,,,GoLearning/demo44.go:10 +0x11
main.main()
e:/,,,GoLearning/demo44.go:41 +0xca
exit status 2
defer
defer特性
- 关键字 defer 用于注册延时调用
- 该调用在方法最后执行( retrun 或者抛出 panic 恐慌之前)
- 多个 defer 函数将按照先进后出的顺序执行
- defer 语句中的变量在 defer 声明时就已经确认了
defer执行顺序
上面说到 defer 函数将按照先进先出的顺序执行,下面用一段代码进行解释:
其实这就是类似于压栈的操作,先声明的 defer 语句先进入链表,这个链表与该 defer 语句所属的函数是对应的,该链表遵循先入后出的原则。
func main() {
defer func() { fmt.Println("1111") }()
defer func() { fmt.Println("2222") }()
defer func() { fmt.Println("3333") }()
defer func() { fmt.Println("4444") }()
}
---------------------------
4444
3333
2222
1111
defer语句中的变量
defer 语句中的变量在 defer 声明时就已经确认了,即如果我们在调用中传递了参数,这个参数会立即被计算
如下代码:
通过打印记录可以看出来,我们 defer 语句里面作为参数的函数被立即执行了。
然后真正的 defer 语句在函数最后被调用,且保持先进后出的原则。
func paramTest(index int) int {
fmt.Println(index)
return index + 100
}
func main() {
defer paramTest(paramTest(1))
defer paramTest(paramTest(2))
}
-----------------------
1
2
102
101
defer中的闭包
同时我们要小心闭包带来的影响。
如下:我们发现打印结果全是 5
原因是 defer 语句执行的匿名函数没有接受任何参数,只是直接使用了外部作用域中的变量 i,只有在main方法的最后,当真正执行这个调用的时候,才会去寻找这个 i 的值,此时 i 已经变成了 5
func main() {
for i := 0; i < 5; i++ {
defer func() { fmt.Println(i) }()
}
}
-----------------------
5
5
5
5
5
解决方法就是,将 i 作为匿名函数的参数传入进去。
func main() {
for i := 0; i < 5; i++ {
defer func(i int) { fmt.Println(i) }(i)
}
}
--------------------
4
3
2
1
0
recover
用来控制一个 goroutine 的 panicking 行为,捕获 panic,并恢复系统运行。要注意 recover 只能恢复当前协程的 panic 恐慌。
一般和 defer 配合使用,因为要确保系统能够走到 recover 函数。
异常处理实现
一般来说,我们要尽量的将业务可能出现的报错区分开来,对于业务逻辑上允许的错误,我们使用 error
示例一:获取并打印 panic
defer 语句一般定义在函数的最开始,为确保一定会执行。
func panicCreater() {
is := 0
fmt.Println(10 / is)
}
func undertake() {
if p := recover(); p != nil {
fmt.Printf("panic printer: %s\n", p)
} else {
fmt.Printf("panic printer: %s\n", "none panic")
}
}
func main() {
defer undertake()
//触发抛出异常
panicCreater()
}
------------------------
panic printer: runtime error: integer divide by zero
示例二:获取 panic 并转换为 error
func test() (err error) {
defer func() {
if p := recover(); p != nil {
err = fmt.Errorf("panic to error: %s", p)
}
}()
panicCreater()
return
}
func main() {
err := test()
fmt.Println(err)
}
------------------------
panic printer: runtime error: integer divide by zero
协程之能处理当前协程的 panic
当前协程不能获取到其他协程抛出的 panic 恐慌。
如下,系统直接抛出恐慌、退出运行
func main() {
defer undertake()
for i := 0; i < 10; i++ {
go func() {
panicCreater()
}()
}
time.Sleep(1000)
}
---------------------------
panic: runtime error: integer divide by zero
goroutine 1 [running]:
main.panicCreater()
e:/,,,GoLearning/demo44.go:32 +0x11
main.main()
e:/,,,GoLearning/demo44.go:42 +0x2a
exit status 2
对于协程内产生的恐慌,我们需要在当前协程内去处理:
func main() {
for i := 0; i < 5; i++ {
go func() {
defer undertake()
panicCreater()
}()
}
time.Sleep(1000)
}
------------------------------
panic printer: runtime error: integer divide by zero
panic printer: runtime error: integer divide by zero
panic printer: runtime error: integer divide by zero
panic printer: runtime error: integer divide by zero
panic printer: runtime error: integer divide by zero