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
相关文章
|
1月前
|
Go
Go语言中defer的执行顺序详解
【2月更文挑战第22天】
28 4
|
1月前
|
存储 消息中间件 大数据
Go语言在大数据处理中的实际应用与案例分析
【2月更文挑战第22天】本文深入探讨了Go语言在大数据处理中的实际应用,通过案例分析展示了Go语言在处理大数据时的优势和实践效果。文章首先介绍了大数据处理的挑战与需求,然后详细分析了Go语言在大数据处理中的适用性和核心技术,最后通过具体案例展示了Go语言在大数据处理中的实际应用。
|
1月前
|
负载均衡 算法 数据库连接
Go语言性能优化实践:案例分析与解决方案
【2月更文挑战第18天】本文将通过具体的案例分析,探讨Go语言性能优化的实践方法和解决方案。我们将分析几个典型的性能瓶颈问题,并详细介绍如何通过优化代码、调整并发模型、改进内存管理等方式来提升程序的性能。通过本文的学习,读者将能够掌握一些实用的Go语言性能优化技巧,为实际项目开发中的性能优化工作提供指导。
|
2月前
|
监控 安全 Java
Go语言学习笔记(一)
Go语言学习笔记(一)
106 1
|
5天前
|
Go 开发者
Golang深入浅出之-Go语言 defer、panic、recover:异常处理机制
Go语言中的`defer`、`panic`和`recover`提供了一套独特的异常处理方式。`defer`用于延迟函数调用,在返回前执行,常用于资源释放。它遵循后进先出原则。`panic`触发运行时错误,中断函数执行,直到遇到`recover`或程序结束。`recover`在`defer`中捕获`panic`,恢复程序执行。注意避免滥用`defer`影响性能,不应对可处理错误随意使用`panic`,且`recover`不能跨goroutine捕获panic。理解并恰当使用这些机制能提高代码健壮性和稳定性。
16 2
|
1月前
|
Kubernetes Go 开发者
Go语言与Docker容器结合的实践应用与案例分析
【2月更文挑战第23天】本文通过分析实际案例,探讨了Go语言与Docker容器技术结合的实践应用。通过详细阐述Go语言在容器化环境中的开发优势,以及Docker容器技术在Go应用部署中的重要作用,本文旨在为读者提供Go语言与Docker容器结合的具体实现方法和实际应用场景。
|
1月前
|
SQL 机器学习/深度学习 缓存
Go语言Web应用实战与案例分析
【2月更文挑战第21天】本文将通过实战案例的方式,深入探讨Go语言在Web应用开发中的应用。我们将分析一个实际项目的开发过程,展示Go语言在构建高性能、可扩展Web应用方面的优势,并分享在开发过程中遇到的问题和解决方案,为读者提供宝贵的实战经验。
|
1月前
|
存储 分布式计算 算法
GO学习笔记之表达式
GO学习笔记之表达式
33 1
|
1月前
|
存储 编译器 Go
GO语言学习笔记
GO语言学习笔记
23 1
|
2月前
|
Go
Go语言中的异常处理:理解panic与recover
【2月更文挑战第7天】Go语言虽然以简洁和直接错误处理机制而著称,但它也提供了`panic`和`recover`这两个内置函数来处理程序中的异常情况。本文将深入探讨Go语言中的异常处理机制,包括`panic`和`recover`的使用场景、原理以及最佳实践,帮助读者更好地理解如何在Go中处理异常情况。