Go 语言编程 — panic 和 recover

举报
云物互联 发表于 2021/08/05 23:11:41 2021/08/05
【摘要】 目录 文章目录 目录defer,panic 和 recoverpanicrecover通过 panic + recover 来简化错误处理 defer,panic 和 recover Golang 中常规的错误处理方式是直接 return error 给调用者,再由调用者决定后续的程序逻辑,或捕获、或终止、或恢复。但对于一些无法恢复的错误,返回 err...

目录

defer,panic 和 recover

Golang 中常规的错误处理方式是直接 return error 给调用者,再由调用者决定后续的程序逻辑,或捕获、或终止、或恢复。但对于一些无法恢复的错误,返回 error 也没有意义,此时可以考虑使用 panic(惊恐)语句,表示一种自动触发的错误。除了在代码中主动触发 panic,在程序运行的过程中也可能会因为出现某些错误而触发 panic,例如:数组越界。

panic 会退出当前正在执行的程序(注意,不只是 Goroutine 协程),但是与 os.Exit(-1) 此类 “义无反顾” 的退出不同,panic 的退出更加的 “井然有序”,panic 会先递归的处理完当前 Goroutine 已经 defer 上去的任务,执行完毕后再打印调用栈,最终调用 exit(-2) 退出整个进程。

相对的,recover(恢复)关键字可以恢复 panic 造成的程序终止,并且 recover 是一个只能在 defer 函数中发挥作用的函数,在其他作用域中调用不会发挥任何作用。

可见,defer、panic、recover 三者的组合可以完成一种灵活的程序流控制。比如:在 defer 中通过 recover 截取 panic,从而达到 try/catch 的效果。

panic

panic 允许传入一个实参,通常是错误信息,panic 会打印这个字符串,以及触发 panic 的调用栈。

package main

import ( "fmt" "os" "time"
)

func main() { var result string = "successfully." var user = os.Getenv("USER_") go func() { defer func() { fmt.Println("defer here.") }() if user == "" { panic("should set user env.") } }() time.Sleep(1 * time.Second) fmt.Printf("get result %d\r\n", result)
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

结果:

$ go run main.go
defer here.
panic: should set user env.

goroutine 18 [running]:
main.main.func1(0x0, 0x0)
	/Users/mickeyfan/workspace/go/src/test/main.go:20 +0x79
created by main.main
	/Users/mickeyfan/workspace/go/src/test/main.go:14 +0x52
exit status 2

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

上述例子可以看出,panic 终止了程序,并且是在执行完挂载到 Goroutine 的 defer 之后退出的,同时还打印了错误堆栈。注意,当一个 Goroutine 挂载了多个 defer 的话,其执行顺序是后进先出的,即逆序执行。

另外,panic 仅能保证递归当前 Goroutine 下的所有 defer 都会被调用,但不保证其他的 Goroutine。例子:

package main

import ( "fmt" "os" "time"
)

func main() { defer fmt.Println("defer main") // will this be printed when panic? var result string = "successfully." var user = os.Getenv("USER_") go func() { defer fmt.Println("defer caller") func() { defer func() { fmt.Println("defer here.") }() if user == "" { panic("should set user env.") } }() }() time.Sleep(1 * time.Second) fmt.Printf("get result %d\r\n", result)
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

结果:

defer here.
defer caller
panic: should set user env.

goroutine 18 [running]:
main.main.func1.1(0x0, 0x0)
	/Users/mickeyfan/workspace/go/src/test/main.go:23 +0x79
main.main.func1(0x0, 0x0)
	/Users/mickeyfan/workspace/go/src/test/main.go:25 +0x9b
created by main.main
	/Users/mickeyfan/workspace/go/src/test/main.go:15 +0xcb
exit status 2

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

上述例子中,存在一个 main defer,main 会 sleep 1s 然后 return,在这个过程中 Goroutine 已经 panic 了,还没得当 main return,程序已经终止,所有已没有执行 main defer。而同一个 Goroutine 下挂载的 defer 都执行到了,并且是逆序执行的。

recover

recover,顾名思义是恢复被 panic 退出的程序流,是 Golang 的 handle panic(异常处理)机制。

try { //
} catch (Exeption e) { //
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5

recover 只在 defer 函数中有效,如果不是在 refer 上下文中调用 recover 会直接返回 nil。

例子:

package main

import ( "fmt" "os" "time"
)

func main() { defer fmt.Println("defer main") // will this be printed when panic? var user = os.Getenv("USER_") go func() { defer fmt.Println("defer caller") func() { defer func() { fmt.Println("defer here.") if err := recover(); err != nil { fmt.Println("recover success.") } }() if user == "" { panic("should set user env.") } }() }() time.Sleep(1 * time.Second) fmt.Println("get result")
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

结果:

$ go run main.go
defer here.
recover success.
defer caller
get result
defer main

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

通过 panic + recover 来简化错误处理

// Error is the type of a parse error; it satisfies the error interface.
type Error string

func (e Error) Error() string { return string(e)
}

// error is a method of *Regexp that reports parsing errors by
// panicking with an Error.
func (regexp *Regexp) error(err string) { panic(Error(err))
}

// Compile returns a parsed representation of the regular expression.
func Compile(str string) (regexp *Regexp, err error) { regexp = new(Regexp) // doParse will panic if there is a parse error. defer func() { if e := recover(); e != nil { regexp = nil // Clear return value. err = e.(Error) // Will re-panic if not a parse error. } }() return regexp.doParse(str), nil
}


if pos == 0 { re.error("'*' illegal at start of expression")
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

在 Compile() 中,遇到问题只管 panic 就好,recover() 会捕捉 Error 类型的 panic,并将其转为 string 返回给调用者,调用 Compile 时并不会感知到 Compile 的 panic。而在其他场景中,例如:数组越界,在 recover 中做类型判断的时候仍然会 panic,并不会因为有 recover 就掩盖了。

另外,这个例子也说明了在 recover 中如果再次 panic,该 panic 还是会再次陷入,除非在 recover 中挂 defer 去再次 recover,否则程序就真的退出了。

通过这样的处理,就不再需要 err != nil 满天飞了,有问题 panic 就可以。

文章来源: is-cloud.blog.csdn.net,作者:范桂飓,版权归原作者所有,如需转载,请联系作者。

原文链接:is-cloud.blog.csdn.net/article/details/107449462

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。