Go学习笔记-defer、panic、recover分析

简介: Go学习笔记-defer、panic、recover分析

1.例反汇编的两种命令

go tool compile -S a.go
go tool compile -N -l a.go
go tool objdump a.o

Tips:两者的区别是汇编格式不一样,-N -l加上去除优化和内联选项

2.go语言函数调用栈

2.1 CALL 和 RET 作用

当 A 函数调用 B 函数时,编译器就会对应生成一条 CALL 指令,程序执行到 CALL 指令时,就会跳转到被调用函数的入口处开始执行,每个被调用的函数执行结束会有一条 RET 指令,负责函数结束后跳回调用处

3.go语言函数调用规约

caller                                                                                 
                                 +------------------+                                                                         
                                 |                  |                                                                         
       +---------------------->  --------------------                                                                         
       |                         |                  |                                                                         
       |                         | caller parent BP |                                                                         
       |           BP(pseudo SP) --------------------                                                                         
       |                         |                  |                                                                         
       |                         |   Local Var0     |                                                                         
       |                         --------------------                                                                         
       |                         |                  |                                                                         
       |                         |   .......        |                                                                         
       |                         --------------------                                                                         
       |                         |                  |                                                                         
       |                         |   Local VarN     |                                                                         
                                 --------------------                                                                         
 caller stack frame              |                  |                                                                         
                                 |   callee arg2    |                                                                         
       |                         |------------------|                                                                         
       |                         |                  |                                                                         
       |                         |   callee arg1    |                                                                         
       |                         |------------------|                                                                         
       |                         |                  |                                                                         
       |                         |   callee arg0    |                                                                         
       |                         ----------------------------------------------+   FP(virtual register)                       
       |                         |                  |                          |                                              
       |                         |   return addr    |  parent return address   |                                              
       +---------------------->  +------------------+---------------------------    <-------------------------------+         
                                                    |  caller BP               |                                    |         
                                                    |  (caller frame pointer)  |                                    |         
                                     BP(pseudo SP)  ----------------------------                                    |         
                                                    |                          |                                    |         
                                                    |     Local Var0           |                                    |         
                                                    ----------------------------                                    |         
                                                    |                          |                                              
                                                    |     Local Var1           |                                              
                                                    ----------------------------                            callee stack frame
                                                    |                          |                                              
                                                    |       .....              |                                              
                                                    ----------------------------                                    |         
                                                    |                          |                                    |         
                                                    |     Local VarN           |                                    |         
                                  SP(Real Register) ----------------------------                                    |         
                                                    |                          |                                    |         
                                                    |                          |                                    |         
                                                    |                          |                                    |         
                                                    |                          |                                    |         
                                                    |                          |                                    |         
                                                    +--------------------------+    <-------------------------------+         
                                                              callee
-----------------                                           
                       current func arg0                                           
                       ----------------- <----------- FP(pseudo FP)                
                        caller ret addr                                            
                       +---------------+                                           
                       | caller BP(*)  |                                           
                       ----------------- <----------- SP(pseudo SP,实际上是当前栈帧的 BP 位置)
                       |   Local Var0  |                                           
                       -----------------                                           
                       |   Local Var1  |                                           
                       -----------------                                           
                       |   Local Var2  |                                           
                       -----------------                -                          
                       |   ........    |                                           
                       -----------------                                           
                       |   Local VarN  |                                           
                       -----------------                                           
                       |               |                                           
                       |               |                                           
                       |  temporarily  |                                           
                       |  unused space |                                           
                       |               |                                           
                       |               |                                           
                       -----------------                                           
                       |  call retn    |                                           
                       -----------------                                           
                       |  call ret(n-1)|                                           
                       -----------------                                           
                       |  ..........   |                                           
                       -----------------                                           
                       |  call ret1    |                                           
                       -----------------                                           
                       |  call argn    |                                           
                       -----------------                                           
                       |   .....       |                                           
                       -----------------                                           
                       |  call arg3    |                                           
                       -----------------                                           
                       |  call arg2    |                                           
                       |---------------|                                           
                       |  call arg1    |                                           
                       -----------------   <------------  hardware SP 位置           
                       | return addr   |                                           
                       +---------------+

函数执行时需要有足够的内存空间,供它存放局部变量、参数、返回值,这段空间对应到虚拟地址空间的

4.defer实现原理分析

4.1 例子1

package main
import "fmt"
func main(){
    var a,b int
    b = incr(a)
    fmt.Println(a,b)
}
func incr(a int) (b int) {
    defer func() {
        a++
        b++
    }()
    a++
    return a
}

函数调用栈示意图:

4.2 例子2

package main
import "fmt"
func main(){
    a,b := 1,2
    c := incr(a,b)
    fmt.Println(c)
}
func incr(m,n int) int {
    var b int
    a := m + n
    defer func(x int) {
        a = a + x
        b++
    }(a + 1)
    a = 99
    b = a
    return b
}

函数调用栈示意图:

使用如下命令查看函数调用栈的汇编:

go tool compile -N -l a.go
go tool objdump a.o

5.go panic 和 recover 源码分析

5.1 Goroutine结构体示意图

  • panic 能立即终止程序,并在当前 Goroutine 中递归执行调用方的 defer
  • recover 可以终止 panic 造成的程序崩溃,并且它只能在defer中起到作用

5.2 panic结构体

5.3 defer结构体

5.4 例子1

例子 t1.go

package main
import "fmt"
func main()  {
    defer A()
    defer B()
    defer C()
    panic("panic main")
    fmt.Println("func main")
}
func A()  {
    fmt.Println("func A")
}
func B()  {
    fmt.Println("func B")
}
func C(){
    fmt.Println("func C")
}

使用如下命令可以查看对应的底层汇编和函数调用栈信息:

go tool compile -N -l t1.go
go tool objdump t1.o

可以看出来遇到 panic 时,底层调用的是 runtime.gopanic,其对应的函数做如下事情:

  • (1)往Goroutine的panic链表表头插入panic
  • (2)循环执行Goroutine的_defer链表
  • (3)_panic 执行 defer时会先把 started 字段置为 true
  • (4)把 _defer 结构体 _panic 字段指针指向当前的执行 _panic 的结构体
情况一:panic 正常执行defer
情况二:panic 执行 defer 中有 panic

5.5 panic执行defer中有panic

例子 t2.go

package main
import "fmt"
func main()  {
    defer A()
    defer B()
    defer C()
    panic("panic main")
    fmt.Println("func main")
}
func A()  {
    fmt.Println("func A")
}
func B()  {
    panic("panic B")
    fmt.Println("func B")
}
func C(){
    fmt.Println("func C")
}
  • 该例子前面步骤和例子 t1.go 中一样,defer C节点被正常执行会被移除
  • 执行到 defer B 的时候,该defer 节点B.startred=true,B.panic = &panicB, 并且 func B() 函数中有 panic,会调用底层 runtime.gopanic,执行和t1.go中相同的动作
  • func B() 中的 panicB 会向 goroutine 中的 _panic 链表头部插入 panicB 节点
  • panicB 会遍历 defer 链表,并且遍历到第一个 defer B 的时候,发现 defer B.startred 字段已经被设置为 true(是之前由panicA 设置的),此时就会把defer B.panic 指向的 panicA 结构体中的 aborted 字段设置为true,表示 panicA 被终止了
  • 移除 defer B节点,继续正常执行 defer A
  • 接下来打印 panic 信息,_panic 链表是从链表尾部向头部打印的,所以是先打印 panicA,在打印 panicB的信息

5.6 panic执行defer中有recover

例子t3.go

package main
import "fmt"
func main()  {
    defer A()
    defer B()
    defer C()
    panic("panic main")
    fmt.Println("func main")
}
func A()  {
    x := recover()
    fmt.Println("func A,recover:",x)
}
func B()  {
    panic("panic B")
    fmt.Println("func B")
}
func C(){
    fmt.Println("func C")
}
  • 该例子中前面步骤参考 t1.go 和 t2.go,最后panicB 在遍历defer A 节点时,触发 func A() 函数,该函数中,有recover函数
  • recover() 函数只做一件事,把当前执行的panic节点的 recovered字段设置为true,并且此时recover()函数捕获到的信息为panicB的信息,即x=”panic B”,该例子中 func A() 函数中的 recover 设置的是 panicB节点中的 recovered字段,此时panicB节点已经被恢复,就会被移除,移除后会跳出panicB的处理流程,接着panicA的处理流程

5.7 panic执行defer中有recover&panic

例子t4.go

package main
import "fmt"
func main()  {
    defer A()
    defer B()
    defer C()
    panic("panic main")
    fmt.Println("func main")
}
func A()  {
    x := recover()
    fmt.Println("func A,recover:",x)
    panic("panic A")
}
func B()  {
    panic("panic B")
    fmt.Println("func B")
}
func C(){
    fmt.Println("func C")
}
  • 该例子中前面步骤参考 t1.go、t2.go、t3.go,当执行到 recover 函数时,panic B被recover()函数设置为已回复,但recover()函数后面还有 panicR
  • panicR 会在goroutine 头部插入 panic 节点,并且 panicR 也会执行底层 runtime.gopanic函数,遍历defer 链表,发现此时 defer A startred 为 true,且会把 defer A链表指向的 panic B设置为已终止,并且移除 defer A,此时 panic B 的 aborted 和 recovered 都是true
  • 接下来就是打印 panic 信息,和之前一样,_panic 链表也是从链表尾部开始的,首先打印 panic A的信息,由于 panic B被恢复了,所以先打印 panic B的信息时候,会提示[recovered],然后打印 panic R
相关文章
Go语言的条件控制语句及循环语句的学习笔记
本文是Go语言的条件控制语句和循环语句的学习笔记,涵盖了if语句、if-else语句、if嵌套语句、switch语句、select语句以及for循环和相关循环控制语句的使用方法。
Go语言的条件控制语句及循环语句的学习笔记
|
2月前
|
存储 Go
Go: struct 结构体类型和指针【学习笔记记录】
本文是Go语言中struct结构体类型和指针的学习笔记,包括结构体的定义、成员访问、使用匿名字段,以及指针变量的声明使用、指针数组定义使用和函数传参修改值的方法。
|
2月前
|
人工智能 算法 搜索推荐
Go学习笔记-代码调
近年来,人工智能技术飞速发展,Cody作为由Sourcegraph开发的一款AI驱动编码助手,应运而生。它不仅提供代码预测与补全,还能深度理解代码上下文,为开发者提供准确建议,提升编码效率和质量。Cody能识别潜在错误并提出修复建议,缩短调试时间,同时进行智能代码审查,帮助优化代码结构和风格。未来,随着AI技术进步,Cody将不断学习优化,成为开发者不可或缺的伙伴,推动编程领域的创新与发展。
33 0
|
3月前
|
存储 Go
掌握 Go 语言的 defer 关键字
掌握 Go 语言的 defer 关键字
|
3月前
|
Go
实验深度理解Go中try...catch...的panic、defer、recover用法
文章通过实验代码演示了Go语言中如何使用panic、defer和recover函数来模拟try...catch...的异常处理机制,并详细解释了每个函数的作用和在异常处理中的使用场景。
35 0
|
6月前
|
Java Go 区块链
【Go语言专栏】Go语言中的延迟执行与defer语句
【4月更文挑战第30天】Go语言的延迟执行与defer语句用于资源释放和错误处理。defer通过关键字定义,函数返回时执行,顺序与定义相反。参数在定义时求值。应用包括资源释放、错误处理、成对操作和函数包装,是Go编程的关键特性。
54 0
|
6天前
|
存储 JSON 监控
Viper,一个Go语言配置管理神器!
Viper 是一个功能强大的 Go 语言配置管理库,支持从多种来源读取配置,包括文件、环境变量、远程配置中心等。本文详细介绍了 Viper 的核心特性和使用方法,包括从本地 YAML 文件和 Consul 远程配置中心读取配置的示例。Viper 的多来源配置、动态配置和轻松集成特性使其成为管理复杂应用配置的理想选择。
23 2
|
4天前
|
Go 索引
go语言中的循环语句
【11月更文挑战第4天】
13 2
|
4天前
|
Go C++
go语言中的条件语句
【11月更文挑战第4天】
16 2
|
9天前
|
Ubuntu 编译器 Linux
go语言中SQLite3驱动安装
【11月更文挑战第2天】
31 7