Ezreal 书架 Ezreal 书架
Home
  • 《Go程序员面试笔试宝典》
  • 《RabbitMQ 实战指南》
  • 《深入理解kafka》
  • MySQL45讲
  • 透视HTTP协议
  • 结构化数据的分布式存储系统
  • Raft 共识算法
Home
  • 《Go程序员面试笔试宝典》
  • 《RabbitMQ 实战指南》
  • 《深入理解kafka》
  • MySQL45讲
  • 透视HTTP协议
  • 结构化数据的分布式存储系统
  • Raft 共识算法
  • 逃逸分析

  • 延迟语句

    • 延迟语句是什么
    • 延迟语句的执行顺序是什么
    • 如何拆解延迟语句
    • 如何确定延迟语句的参数
    • 闭包是什么
    • 延迟语句如何配合恢复语句
    • defer 链如何被遍历执行
    • 为什么无法从父 goroutine 恢复子 goroutine 的 panic
  • 数据容器

  • 通道

  • 接口

  • unsafe

  • context

  • Go程序员面试笔试宝典
  • 延迟语句
ezreal_rao
2023-04-27

延迟语句如何配合恢复语句

Go 语言被诟病多次的就是它的 error,实际项目里经常出现各种 error 满天飞,正常的代码逻辑里有很多 error 处理的代码块。函数总是会返回一个 error,留给调用者处理;而如果是致命的错误,比如程序执行初始化的时候出问题,最好直接 panic 掉,避免上线运行后出更大的问题。

有些时候,需要从异常中恢复。比如服务器程序遇到严重问题,产生了 panic,这时至少可以在程序崩溃前做一些 “扫尾工作”,比如关闭客户端的连接,防止客户端一直等待等;并且单个请求导致的 panic,也不应该影响整个服务器程序的运行。

panic 会停掉当前正在执行的程序,而不只是当前线程。在这之前,它会有序地执行完当前线程 defer 列表里的语句, 其他协程里定义的 defer 语句不作保证。 所以在 defer 里定义一个 recover 语句,防止程序直接挂掉,就可以起到类似 Java 里 try...catch 的效果。

注意,recover () 函数只在 defer 的函数中直接调用才有效。例如:

func deferRecover() {
	defer fmt.Println("defer main")
	var user = os.Getenv("USER_")
	go func() {
		defer func() {
			fmt.Println("defer caller")
			if err := recover(); err != nil {
				fmt.Println("recover success. err: ", err)
			}
		}()
		func() {
			defer func() {
				fmt.Println("defer here")
			}()
			if user == "" {
				panic("should set user env.")
			}
			// 此处不会执行
			fmt.Println("after panic")
		}()
	}()
	time.Sleep(100)
	fmt.Println("end of main function")
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

程序的执行结果:

end of main function
defer main
defer here
defer caller
recover success. err:  should set user env.
1
2
3
4
5

代码中的 panic 最终会被 recover 捕获到。这样的处理方式在一个 http server 的主流程常常会被用到。一次偶然的请求可能会触发某个 bug,这时用 recover 捕获 panic,稳住主流程,不影响其他请求。

同样,我们再来看几个延伸示例。这些例子都与 recover () 函数的调用位置有关。

考虑以下写法,程序是否能正确 recover 吗?如果不能,原因是什么:

func main() {
    defer f()
    panic(404)
}

func f() {
    if e := recover(); e != nil {
        fmt.Println("recover")
        return
    }
}
1
2
3
4
5
6
7
8
9
10
11

能。在 defer 的函数中调用,生效。

func main() {
    recover()
    panic(404)
}
1
2
3
4

不能。直接调用 recover,返回 nil。

func main() {
    defer recover()
    panic(404)
}
1
2
3
4

不能。要在 defer 函数里调用 recover。

func main() {
    defer func() {
        if e := recover(); e != nil {
            fmt.Println("recover")
        }
    }()
    panic(404)
}
1
2
3
4
5
6
7
8

能。在 defer 的函数中调用,生效。

func main() {
    defer func() {
        recover()
    }()
    panic(404)
}
1
2
3
4
5
6

能。在 defer 的函数中调用,生效。

func main() {
    defer func() {
        defer func() {
            recover()
        }()
    }()
    panic(404)
}
1
2
3
4
5
6
7
8

不能。多重 defer 嵌套。

#延迟语句#defer
上次更新: 5/9/2023, 10:58:32 AM
闭包是什么
defer 链如何被遍历执行

← 闭包是什么 defer 链如何被遍历执行→

最近更新
01
为什么我的MySQL会抖一下
07-15
02
HTTP 性能优化面面观
07-12
03
WebSocket:沙盒里的 TCP
07-12
更多文章>
Theme by Vdoing | Copyright © 2022-2024 Ezreal Rao | CC BY-NC-SA 4.0
豫ICP备2023001810号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式