每次学习并整理一个Golang的知识点,每天进步一点点🤔。今天我们来看下defer函数相关的。
代码片段
看如下这段代码,并猜测输出结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
package main
import "fmt"
func deferCall() {
defer func() { fmt.Println("打印前") }()
defer func() { fmt.Println("打印中") }()
defer func() { fmt.Println("打印后") }()
panic("触发异常")
}
func main() {
fmt.Println("答案:")
deferCall()
}
|
思考后看答案
答疑解惑
代码执行结果如下:
1
2
3
4
5
6
7
8
9
10
|
答案:
打印后
打印中
打印前
panic: 触发异常
goroutine 1 [running]:
main.deferCall()
...
|
我们可以看到一个函数中可以同时存在多个defer语句,需要注释意的是,defer语句的调用同栈一样,遵循先进后出的原则,最后一个defer语句将最先被执行。不过我们这里的代码片段只做简单的展示用,当你需要为defer语句到底哪个先执行这种细节而烦恼的时候,说明你的代码架构可能需要调整一下了。
上述结果中,我们也看到panic是最后执行的。当在一个函数执行过程中调用panic()函数时,正常的函数执行流程将立即终止,但函数中,之前使用defer关键字延迟执行的语句将正常展开执行,之后该函数将返回到调用函数,并导致逐层向上执行panic流程,直到所࡚属的goroutine中所有正在执行的函数被终止。错误信息将被报告,包括在调用panic()函数时传入的参数,这个过程称为错误处理流程。
深入源码
当然最好的答案,莫过于看下golang中panic的实现源码,这有助于我们更好的记忆;
panic源码部分展示:
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
|
func Goexit() {
gp := getg() // getg()返回当前协程的 g 结构体指针,g 结构体描述 goroutine
var p _panic // 为Goexit创建一个panic对象
p.goexit = true
p.link = gp._panic
gp._panic = (*_panic)(noescape(unsafe.Pointer(&p)))
addOneOpenDeferFrame(gp, getcallerpc(), unsafe.Pointer(getcallersp()))
for {
d := gp._defer // 获取当前协程defer链表的头节点
if d == nil {
break // 当前协程的defer都被执行后,defer链表为,此时退出for循环
}
if d.started { // 发生panic后,在defer中又遇到panic(),则会进入这个代码块
if d._panic != nil {
d._panic.aborted = true
d._panic = nil
}
if !d.openDefer {
d.fn = nil
gp._defer = d.link
freedefer(d)
continue
}
}
d.started = true
// 此处省略
}
}
|
从上述不完整的代码片段中,我们也可以看到,发生panic时,先进入了一个for循环,当defer都被执行完成后,再退出循环,继续后面的逻辑匹配。相信看到这里,panic和defer的执行顺序就一目了然了。
欢迎转发和评论。更多优质原创文章,欢迎关注微信公众号“IYue爱月”或扫描下方二维码:
文章作者
xujpxm
上次更新
2020-06-18
许可协议
CC BY-NC-ND 4.0 非商业转载请注明出处(作者,原文链接),商业转载请联系作者授权,谢谢!