Go学习笔记 -- 异常处理

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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mingvvv

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值