一些恐慌/恢复用例
恐慌和恢复(panic/recover)已经在之前的文章中介绍过了(第13章)。 下面将展示一些恐慌/恢复用例。
这可能是最常见的panic/recover用例了。 此用例广泛地使用于并发程序中,尤其是响应大量用户请求的应用。
一个例子:
运行此服务器程序,并在另一个终端窗口运行telnet localhost 12345
,我们可以观察到服务器程序不会因为客户连接处理协程中的产生的恐慌而导致崩溃。
当在一个协程将要退出时,程序侦测到此协程是因为一个恐慌而导致此次退出时,我们可以立即重新创建一个相同功能的协程。 一个例子:
package main
import "log"
import "time"
func shouldNotExit() {
for {
time.Sleep(time.Second) // 模拟一个工作负载
// 模拟一个未预料到的恐慌。
if time.Now().UnixNano() & 0x3 == 0 {
panic("unexpected situation")
}
}
}
func NeverExit(name string, f func()) {
defer func() {
if v := recover(); v != nil { // 侦测到一个恐慌
log.Printf("协程%s崩溃了,准备重启一个", name)
go NeverExit(name, f) // 重启一个同功能协程
}
f()
}
func main() {
log.SetFlags(0)
go NeverExit("job#A", shouldNotExit)
go NeverExit("job#B", shouldNotExit)
select{} // 永久阻塞主线程
}
有时,我们可以使用panic
/recover
函数调用来模拟跨函数跳转,尽管一般这种方式并不推荐使用。 这种跳转方式的可读性不高,代码效率也不是很高,唯一的好处是它有时可以使代码看上去不是很啰嗦。
在下面这个例子中,一旦一个恐慌在一个内嵌函数中产生,当前协程中的执行将会跳转到延迟调用处。
一个例子:
func doSomething() (err error) {
defer func() {
err, _ = recover().(error)
}()
doStep1()
doStep2()
doStep3()
doStep4()
doStep5()
return
}
// 在现实中,各个doStepN函数的原型可能不同。
// 每个doStepN函数的行为如下:
// 以示不需继续;
// * 如果本步失败,则调用panic(err)来制造一个恐慌
// 以示不需继续;
// * 不制造任何恐慌表示继续下一步。
func doStepN() {
...
if err != nil {
panic(err)
}
...
if done {
panic(nil)
}
}
但是,这种panic
/recover
函数调用的使用方式一般并不推荐使用,因为它的效率略低一些,并且这种用法不太符合Go编程习俗。
另外需要注意的是:从今后某个Go版本开始(很可能是v1.21),一个panic(nil)
调用将。 从那时开始,上面代码中的延迟函数调用应该被重写为:
func doSomething() (err error) {
defer func() {
err, _ = recover().(error)
if e := (*runtime.PanicNilError)(nil); errors.As(err, &e) {
err = nil
}
}()
doStep1()
...
}
本书由老貘历时三年写成。目前本书仍在不断改进和增容中。你的赞赏是本书和Go101.org网站不断增容和维护的动力。
(请搜索关注微信公众号“Go 101”或者访问获取本书最新版)