Chinaunix首页 | 论坛 | 博客
  • 博客访问: 304228
  • 博文数量: 21
  • 博客积分: 250
  • 博客等级: 二等列兵
  • 技术积分: 484
  • 用 户 组: 普通用户
  • 注册时间: 2010-10-06 23:10
个人简介

程序猿

文章分类

全部博文(21)

文章存档

2016年(17)

2014年(3)

2013年(1)

分类: LINUX

2016-04-04 15:45:09

上一篇讲了golang的错误处理,这篇讲讲异常处理。
错误和异常的区别是,错误是业务逻辑的一部分,异常不是。而且异常有可能终止业务逻辑。在golang中,异常不要用来代替错误,更不要用来控制流程。
golang提供了panic, recover和defer来实现异常的抛出和捕捉。

具体流程是这样的:
函数中调用panic()抛出异常后,程序会马上中止当前的运行,跳转到延时调用(defer语句),如果defer中调用了recover(),则异常被捕捉,程序继续执行;否则,当前函数的defer执行完后返回到上一个调用函数执行defer,如果到最顶层函数都没遇到recover(),程序崩溃打印出调试信息并终止。


下面分别简单介绍defer, panic, recover

defer

defer能让语句推迟到函数返回时再执行。如:
  1. package main
  2. import (
  3.    "fmt"
  4. )
  5. func fun() {
  6.    defer fmt.Println("defer 1")
  7.    fmt.Println("fun")
  8. }
  9. func main() {
  10.    fun()
  11. }
执行:
  1. # go run defer/main.go
  2. fun
  3. defer 1

当有多个defer语句时,将以栈(即先进后出)的形式执行,把上面的fun()函数修改为:
  1. func fun() {
  2.   defer fmt.Println("defer 1")
  3.   defer fmt.Println("defer 2")
  4.   fmt.Println("fun")
  5. }
执行结果:
  1. # go run defer/main.go
  2. fun
  3. defer 2
  4. defer 1

panic

该函数的参数为interface{}类型,这意味着传入参数可以是任意类型。
当panic()被执行时,参数会被传递给recover()函数,如果没遇到recover()函数,程序崩溃时参数会作为调试信息打印出来。

recover

recover()用来返回捕捉异常,返回panic()时传入的参数。如果之前没执行panic(),recover()总返回nil


看几种异常的情形:

1. 未捕捉异常
  1. package main
  2. import "fmt"
  3. func main() {
  4.    f()
  5.    fmt.Println("Returned normally from f.")
  6. }
  7. func f() {
  8.    fmt.Println("Calling g(0).")
  9.    g(0)
  10.    fmt.Println("Returned normally from g(0).")
  11. }
  12. func g(i int) {
  13.    defer fmt.Println("Defer in g", i)
  14.    fmt.Println("Panic!")
  15.    panic("panic in g")
  16.    fmt.Println("Printing in g", i)
  17. }
运行
  1. # go run recover/panic.go
  2. Calling g(0).

  3. Defer in g 0
  4. panic: panic in g

  5. goroutine 1 [running]:
  6. panic(0x4b8ee0, 0xc82000a300)
  7.         /usr/local/go/src/runtime/panic.go:464 +0x3e6
  8. main.g(0x0)
  9.         /root/go/src/github.com/vv1133/golang_example/recover/panic.go:20 +0x292
  10. main.f()
  11.         /root/go/src/github.com/vv1133/golang_example/recover/panic.go:12 +0xeb
  12. main.main()
  13.         /root/go/src/github.com/vv1133/golang_example/recover/panic.go:6 +0x1c
  14. exit status 2

程序执行时遇到了panic,然后执行defer,没遇到recover,向上层函数返回,直到退出,打印出panic信息和出错时的栈信息。


2. 捕捉异常
将函数g()改为如下:
  1. func g(i int) {
  2.    defer func() {
  3.      if r := recover(); r != nil {
  4.        fmt.Println("Recovered in g", r)
  5.      }
  6.    }()
  7.    defer fmt.Println("Defer in g", i)
  8.    fmt.Println("Panic!")
  9.    panic("panic in g")
  10.    fmt.Println("Printing in g", i)
  11. }
运行
  1. # go run recover/recover.go
  2. Calling g(0).

  3. Defer in g 0
  4. Recovered in g panic in g
  5. Returned normally from g(0).
  6. Returned normally from f.

可见,异常被defer中的recover()捕捉了。此时,程序会继续执行。需要注意,程序继续执行的点是recover()函数,而不是出现panic()的地方。


3. 在上一层函数捕捉异常
  1. package main
  2. import "fmt"
  3. func main() {
  4.    f()
  5.    fmt.Println("Returned normally from f.")
  6. }
  7. func f() {
  8.    defer fmt.Println("Defer in f1")
  9.    defer func() {
  10.      if r := recover(); r != nil {
  11.        fmt.Println("Recovered in f", r)
  12.      }
  13.    }()
  14.    defer fmt.Println("Defer in f2")
  15.    fmt.Println("Calling g(0).")
  16.    g(0)
  17.    fmt.Println("Returned normally from g(0).")
  18. }
  19. func g(i int) {
  20.    defer fmt.Println("Defer in g", i)
  21.    fmt.Println("Panic!")
  22.    panic("panic in g")
  23.    fmt.Println("Printing in g", i)
  24. }
运行
  1. # go run recover/recover2.go
  2. Calling g(0).

  3. Defer in g 0
  4. Defer in f2
  5. Recovered in f panic in g
  6. Defer in f1
  7. Returned normally from f.
结果显示异常在函数f()中被捕捉了。


另外,标准库中有一些以Must开头的函数,这些函数出错时,会调用panic

golang中panic的使用类似于C语言中的assert,常用于不可修复性错误或是遇到问题时立刻停止运行的时候,例如init()函数中。
对于业务逻辑处的panic,可以使用recover捕捉,并转换成错误后返回。

示例代码可以在查看。

阅读(5921) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~