结合ChatGPT辅助学习Go语言底层原理

简介: 在当今的信息时代,编程语言已经成为了科技领域的基础工具之一。其中,Go语言以其简洁、高效和并发性强等优点,在开发者中赢得了广泛的好评。然而,对于初学者或是想要深入理解Go语言底层原理的人来说,直接阅读和理解其源码可能会面临一定困难。我主要从以下几个方面分享一些使用ChatGPT辅助学习Go语言底层源码的小技巧。

在当今的信息时代,编程语言已经成为了科技领域的基础工具之一。其中,Go语言以其简洁、高效和并发性强等优点,在开发者中赢得了广泛的好评。然而,对于初学者或是想要深入理解Go语言底层原理的人来说,直接阅读和理解其源码可能会面临一定困难。我主要从以下几个方面分享一些使用ChatGPT辅助学习Go语言底层源码的小技巧。

1、什么情况下使用ChatGPT?

首先我觉着有很多Go开发工程师和我有类似的困惑,就是想要深入学习Go语言底层原理,但自己学着学着,发现很多知识点非常深奥,又很难找到该领域的专家有足够的时间帮你答疑解惑,这个时候我想到了ChatGPT,当遇到难以理解的难题时,可以把自己的问题更加细化去问ChatGPT,让它提供更加详细的解答,直到自己理解为止。所以我的观点是在学习Go语言底层知识之前,需要自己对Go语言相关基础知识有一定的了解,然后带着疑问向ChatGPT提问,当ChatGPT给出答案时,仅供参考,需要有自己的判断,只要ChatGPT给出的解释相对合理,可以姑且相信它,也可以不断地追问加深理解。我这里主要总结了使用ChatGPT的三个方面:

  • 基础的概念和原理
  • 实际应用示例代码
  • 解读代码

    2、汇编基础

  • 推荐阅读:https://chai2010.cn/asmgo-talk/#/4

  • 推荐阅读:https://go.dev/doc/asm
  • 精简指令集
  • 数据传输: MOV/LEA
  • 跳转指令: CMP/TEST/JMP/JCC
  • 栈指令: PUSH/POP
  • 函数调用指令: CALL/RET
  • 算术指令: ADD/SUB/MUL/DIV
  • 逻辑指令: AND/OR/XOR/NOT
  • 移位指令: SHL/SHR
  • JCC有条件跳转: JEQ/JNE/JLT/JLE/JGT/JGE
  • 还有针对无符号数的比较条件
  • MOV指令
    image.png
    image.png
    注:图片来源于https://chai2010.cn/asmgo-talk/#/4/6
  • 伪寄存器

    • SB: 静态基址指针, 全局符号
    • FP: 帧指针, 参数和局部变量
    • SP: 栈指针, 栈的顶端
    • PC: 程序计数器, 跳转和分支
    • 注:伪寄存器仅仅存在于Go汇编中
      image.png
  • 伪寄存器用法

    • GLOBL text(SB),$1: 全局变量
    • MOVQ a+0(FP) AX: 函数参数
    • MOVQ b+8(SP) AX: 局部变量
    • JMP 2(PC): 向前跳转, 常用于宏函数
    • JMP -2(PC): 向后跳转, 常用于宏函数

3、代码调试工具

3.1 dlv 简介

image.png

  • dlv(delve 的简写) 是一个用于 Go 源代码级调试器。
  • dlv 通过控制进程的执行、计算变量、提供线程/协程的状态、CPU寄存器状态等信息,可以方便地与程序进行交互。
  • 这个工具的目标是为调试 Go 程序提供一个简单但功能强大的界面。
    dlv 将标志传递给正在调试的程序,例如:
dlv exec ./hello -- server --config conf/config.toml
AI 代码解读
  • macOs 使用 brew install dlv 命令下载安装即可。

3.2 dlv debug 常用命令

  • b 打断点,例如使用 b main.main 来打断点
  • p 打印变量
  • n: 执行到下一行
  • c: 跳过此断点
  • args: 打印所有的方法参数
  • locals 打印出所有的本地变量
  • l 列出断点最近几行的代码
  • bp: 展示出所有的断点
  • q: 退出

3.3dlv 调试代码

image.png
image.png

  • dlv 剖析 strings.Contains 函数
    新建 a.go 文件,文件内容如下:
package main

import (
    "fmt"
    "strings"
)

func main()  {
    str := "Go语言是世界上最好的语言"
    if strings.Contains(str,"世界") {
        fmt.Println("字符串中包含","世界")
    }else {
        fmt.Println("字符串中不包含","世界")
    }
}
AI 代码解读

使用 dlv debug a.go 命令可以开始对上述代码进行断点调试:
image.png
image.png
image.png
image.png
image.png

qinshixian@qinshixiandeMacBook-Pro qinshixian % dlv debug a.go
Type 'help' for list of commands.
(dlv) b main.main
Breakpoint 1 set at 0x10acd6f for main.main() ./a.go:8
(dlv) c
> main.main() ./a.go:8 (hits goroutine(1):1 total:1) (PC: 0x10acd6f)
     3:    import (
     4:        "fmt"
     5:        "strings"
     6:    )
     7:
=>   8:    func main()  {
     9:        str := "Go语言是世界上最好的语言"
    10:        if strings.Contains(str,"世界") {
    11:            fmt.Println("字符串中包含","世界")
    12:        }else {
    13:            fmt.Println("字符串中不包含","世界")
(dlv) n
> main.main() ./a.go:9 (PC: 0x10acd86)
     4:        "fmt"
     5:        "strings"
     6:    )
     7:
     8:    func main()  {
=>   9:        str := "Go语言是世界上最好的语言"
    10:        if strings.Contains(str,"世界") {
    11:            fmt.Println("字符串中包含","世界")
    12:        }else {
    13:            fmt.Println("字符串中不包含","世界")
    14:        }
(dlv) n
> main.main() ./a.go:10 (PC: 0x10acd9b)
     5:        "strings"
     6:    )
     7:
     8:    func main()  {
     9:        str := "Go语言是世界上最好的语言"
=>  10:        if strings.Contains(str,"世界") {
    11:            fmt.Println("字符串中包含","世界")
    12:        }else {
    13:            fmt.Println("字符串中不包含","世界")
    14:        }
    15:    }
(dlv) p str
"Go语言是世界上最好的语言"
(dlv) s strings.Contains
> strings.Contains() /Users/qinshixian/.g/go/src/strings/strings.go:61 (PC: 0x10ac1e6)
    56:            s = s[i+len(substr):]
    57:        }
    58:    }
    59:
    60:    // Contains reports whether substr is within s.
=>  61:    func Contains(s, substr string) bool {
    62:        return Index(s, substr) >= 0
    63:    }
    64:
    65:    // ContainsAny reports whether any Unicode code points in chars are within s.
    66:    func ContainsAny(s, chars string) bool {
(dlv) n
> strings.Contains() /Users/qinshixian/.g/go/src/strings/strings.go:62 (PC: 0x10ac20d)
    57:        }
    58:    }
    59:
    60:    // Contains reports whether substr is within s.
    61:    func Contains(s, substr string) bool {
=>  62:        return Index(s, substr) >= 0
    63:    }
    64:
    65:    // ContainsAny reports whether any Unicode code points in chars are within s.
    66:    func ContainsAny(s, chars string) bool {
    67:        return IndexAny(s, chars) >= 0
(dlv) b Contains
Breakpoint 2 set at 0x10ac1e6 for strings.Contains() /Users/qinshixian/.g/go/src/strings/strings.go:61
(dlv) p s
"Go语言是世界上最好的语言"
(dlv) p substr
"世界"
(dlv) s Index
> strings.Index() /Users/qinshixian/.g/go/src/strings/strings.go:1024 (PC: 0x10ac32f)
  1019:        // One string is empty. Are both?
  1020:        return s == t
  1021:    }
  1022:
  1023:    // Index returns the index of the first instance of substr in s, or -1 if substr is not present in s.
=>1024:    func Index(s, substr string) int {
  1025:        n := len(substr)
  1026:        switch {
  1027:        case n == 0:
  1028:            return 0
  1029:        case n == 1:
(dlv) n
> strings.Index() /Users/qinshixian/.g/go/src/strings/strings.go:1025 (PC: 0x10ac36f)
  1020:        return s == t
  1021:    }
  1022:
  1023:    // Index returns the index of the first instance of substr in s, or -1 if substr is not present in s.
  1024:    func Index(s, substr string) int {
=>1025:        n := len(substr)
  1026:        switch {
  1027:        case n == 0:
  1028:            return 0
  1029:        case n == 1:
  1030:            return IndexByte(s, substr[0])
(dlv) n
> strings.Index() /Users/qinshixian/.g/go/src/strings/strings.go:1027 (PC: 0x10ac380)
  1022:
  1023:    // Index returns the index of the first instance of substr in s, or -1 if substr is not present in s.
  1024:    func Index(s, substr string) int {
  1025:        n := len(substr)
  1026:        switch {
=>1027:        case n == 0:
  1028:            return 0
  1029:        case n == 1:
  1030:            return IndexByte(s, substr[0])
  1031:        case n == len(s):
  1032:            if substr == s {
(dlv) p n
6
(dlv) p len(s)
35
(dlv) n
> strings.Index() /Users/qinshixian/.g/go/src/strings/strings.go:1029 (PC: 0x10ac3a4)
  1024:    func Index(s, substr string) int {
  1025:        n := len(substr)
  1026:        switch {
  1027:        case n == 0:
  1028:            return 0
=>1029:        case n == 1:
  1030:            return IndexByte(s, substr[0])
  1031:        case n == len(s):
  1032:            if substr == s {
  1033:                return 0
  1034:            }
(dlv) n
> strings.Index() /Users/qinshixian/.g/go/src/strings/strings.go:1031 (PC: 0x10ac403)
  1026:        switch {
  1027:        case n == 0:
  1028:            return 0
  1029:        case n == 1:
  1030:            return IndexByte(s, substr[0])
=>1031:        case n == len(s):
  1032:            if substr == s {
  1033:                return 0
  1034:            }
  1035:            return -1
  1036:        case n > len(s):
(dlv)
> strings.Index() /Users/qinshixian/.g/go/src/strings/strings.go:1036 (PC: 0x10ac494)
  1031:        case n == len(s):
  1032:            if substr == s {
  1033:                return 0
  1034:            }
  1035:            return -1
=>1036:        case n > len(s):
  1037:            return -1
  1038:        case n <= bytealg.MaxLen:
  1039:            // Use brute force when s and substr both are small
  1040:            if len(s) <= bytealg.MaxBruteForce {
  1041:                return bytealg.IndexString(s, substr)
(dlv) n
> strings.Index() /Users/qinshixian/.g/go/src/strings/strings.go:1038 (PC: 0x10ac4cf)
  1033:                return 0
  1034:            }
  1035:            return -1
  1036:        case n > len(s):
  1037:            return -1
=>1038:        case n <= bytealg.MaxLen:
  1039:            // Use brute force when s and substr both are small
  1040:            if len(s) <= bytealg.MaxBruteForce {
  1041:                return bytealg.IndexString(s, substr)
  1042:            }
  1043:            c0 := substr[0]
(dlv) p bytealg.MaxLen
63
(dlv) n
> strings.Index() /Users/qinshixian/.g/go/src/strings/strings.go:1040 (PC: 0x10ac4e7)
  1035:            return -1
  1036:        case n > len(s):
  1037:            return -1
  1038:        case n <= bytealg.MaxLen:
  1039:            // Use brute force when s and substr both are small
=>1040:            if len(s) <= bytealg.MaxBruteForce {
  1041:                return bytealg.IndexString(s, substr)
  1042:            }
  1043:            c0 := substr[0]
  1044:            c1 := substr[1]
  1045:            i := 0
(dlv) p len(s)
35
(dlv) p bytealg.MaxBruteForce
64
(dlv) n
> strings.Index() /Users/qinshixian/.g/go/src/strings/strings.go:1041 (PC: 0x10ac4ff)
  1036:        case n > len(s):
  1037:            return -1
  1038:        case n <= bytealg.MaxLen:
  1039:            // Use brute force when s and substr both are small
  1040:            if len(s) <= bytealg.MaxBruteForce {
=>1041:                return bytealg.IndexString(s, substr)
  1042:            }
  1043:            c0 := substr[0]
  1044:            c1 := substr[1]
  1045:            i := 0
  1046:            t := len(s) - n + 1
(dlv) s bytealg.IndexString
> internal/bytealg.IndexString() /Users/qinshixian/.g/go/src/internal/bytealg/index_amd64.s:18 (PC: 0x1002840)
Warning: debugging optimized function
    13:        MOVQ DI, R10
    14:        LEAQ ret+48(FP), R11
    15:        JMP  indexbody<>(SB)
    16:
    17:    TEXT ·IndexString(SB),NOSPLIT,$0-40
=>  18:        MOVQ a_base+0(FP), DI
    19:        MOVQ a_len+8(FP), DX
    20:        MOVQ b_base+16(FP), R8
    21:        MOVQ b_len+24(FP), AX
    22:        MOVQ DI, R10
    23:        LEAQ ret+32(FP), R11
(dlv) bp
Breakpoint runtime-fatal-throw (enabled) at 0x1032e20 for runtime.throw() /Users/qinshixian/.g/go/src/runtime/panic.go:1188 (0)
Breakpoint unrecovered-panic (enabled) at 0x1033180 for runtime.fatalpanic() /Users/qinshixian/.g/go/src/runtime/panic.go:1271 (0)
    print runtime.curg._panic.arg
Breakpoint 1 (enabled) at 0x10acd6f for main.main() ./a.go:8 (1)
Breakpoint 2 (enabled) at 0x10ac1e6 for strings.Contains() /Users/qinshixian/.g/go/src/strings/strings.go:61 (0)
(dlv)
AI 代码解读

4、示例代码

go version:go 1.14.15
AI 代码解读
package main

func main() {
   n := 10
   println(read(&n))
}


//go:noinline
func read(p *int) (v int) {
   v = *p
   return
}
AI 代码解读
  • //go:noinline:禁止Go对函数进行内联
  • 内联:内联是一种手动或编译器优化,用于将简短函数的调用替换为函数体本身。这么做的原因是它可以消除函数调用本身的开销,也使得编译器能更高效地执行其他的优化策略
  • 使用objdump工具反编译
    image.png

5、结合ChatGPT反编译调试

  • 反汇编的两种命令
go tool compile -S a.go
或
go tool compile -N -l a.go
go tool objdump a.o

Tips:两者的区别是汇编格式不一样,-N -l加上去除优化和内联选项
AI 代码解读
  • 对示例代码反编译
go build main.go                                 //编译

go tool objdump -S -s "main.read" main      //反编译
AI 代码解读

image.png

  • 图中是用go自带的objdump工具对main.read反编译得到的汇编指令
  • 不懂就问,咱们直接问ChatGPT这段汇编指令的含义(首先需要给ChatGPT一些引导和背景介绍),如下图
    image.png
    image.png
  • 上面解释的很清楚了,为了进一步理解这些指令的含义,追问
    image.png
    image.png
    image.png

6、Go代码原理剖析实战

下面通过列举一些例子,结合ChatGPT来辅助深入理解Go代码实际运行机制,以后遇到类似困惑的时候,都可以通过该方法了解其中的原理。

6.1 指针

  • 指针本身是一个无符号整型
    go version:go 1.14.15
    
    AI 代码解读
package main


func main() {
   n := int32(10)
   println(read32(&n))
}

//go:noinline
func read32(p *int32) (v int32) {
   v = *p
   return
}
AI 代码解读

image.png
image.png

可以看到上述汇编指令中,第一行从参数p中取地址值的操作没变化,只是从AX寄存器中取值的时候,命令有MOVQ(8字节)变为MOVL(4字节),可见不同类型的指针地址本身是一样的类型(无符号整型) 。

  • 取地址
    go version:go 1.14.15
    
    AI 代码解读
package main


var n int


func main() {
   println(addr())
}


//go:noinline
func addr() (p *int) {
   return &n
}
AI 代码解读

image.png

直接问ChatGPT,给出的解释是:

  • 从上图可以看到全局变量n是存在main包的静态基地址上(SB),被不同的函数和代码块共享访问,SB 是静态基地址的缩写,它是指向静态基地址的寄存器。因此,"main.n(SB)" 就是通过 SB 指向 main 包的静态基地址上的 n 变量。
  • LEAQ 指令将全局变量 n 的有效地址存储到 AX 寄存器中,这样 AX 寄存器就包含了 n 变量的地址,可以用于读取或写入该变量的值。
  • LEAQ 指令用于将有效地址存储到一个寄存器中

  • 强制类型转换

    go version:go 1.14.15
    
    AI 代码解读
package main


import "unsafe"


func main() {
   p := 3
   convert(&p)
}


//go:noinline
func convert(p *int) {
   q := (*int32)(unsafe.Pointer(p))
   *q = 0
}
AI 代码解读

image.png
image.png

  • 这段汇编代码是将一个指向int类型变量的指针,转换为指向int32类型变量的指针,并将其所指向的内存空间的值设置为0
  • 把指针的类型强转换为int32后,原本的MOVQ指令变成了MOVL,没有产生任何额外指令,所以转换效率是非常高的

    6.2go 函数调用栈

  • 程序的基本分段

    .data : 有初始化值的全局变量;定义常量。
    .bss : 没有初始化值的全局变量。
    .text : 代码段。
    .rodata: 只读数据段。
    
    AI 代码解读

    image.png
    image.png

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

  • 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   |                                           
                       +---------------+
AI 代码解读

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

  • 示例代码
    go version:go 1.17.2
    
    AI 代码解读
package main

import "fmt"

func main()  {
    a,b := 1,2
    c := sum(a,b)
    fmt.Println(c)
}

func sum(x,y int) (z int) {
    z = x + y
    return z
}
AI 代码解读
  • 入栈和出战规则
    image.png
    image.png

6.3go defer、panic、recover

image.png
image.png

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

  • _defer结构体
    image.png

  • defer 示例代码调试

    go version:go 1.14.15
    
    AI 代码解读

    下面这段关于defer的go代码,你知道defer是如何影响incr函数中的变量a变量b的吗?

    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
    }
    
    AI 代码解读

    函数调用栈示意图:
    image.png

反编译调试:

go tool compile -N -l main.go
go tool objdump mian.o
AI 代码解读

image.png
image.png
image.png
image.png

结合上面的分析incr函数中的变量a和变量b的分析如下:

(1)incr中变量a作为局部变量,值是 m + n 等于 3;

(2)在入栈给的时候,传给 x 的值是 a+1 等于 4;

(3)给a赋值99,a的值变为99;

(4)给b赋值,b的值为99;

(5)赋值给返回地址,取b的值 99;

(6)执行defer函数,a 的值为 a + x,就是 99 + 4,a的值变为 103;

(7)b++,b的值变为100(注意在此之前b的值为99时,已经赋给返回地址了);

综上所述,main函数中的c最终的打印值是 99

Tips:go函数defer执行时是先给返回值地址赋值,再执行defer
AI 代码解读
  • panic 正常执行 defer
    go version:go 1.14.15
    
    AI 代码解读
package main

import "fmt"

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

func A()  {
    fmt.Println("func A")
}

func B()  {
    fmt.Println("func B")
}

func C(){
    fmt.Println("func C")
}
AI 代码解读

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

go tool compile -N -l t1.go
go tool objdump t1.o
AI 代码解读

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

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

    情况一:panic 正常执行defer,此时被正常执行的_defer节点会被移除
    情况二:panic 执行 defer 中有 panic
    
    AI 代码解读

    image.png

  • panic 执行 defer 中有 panic

    go version:go 1.14.15
    
    AI 代码解读
package main

import "fmt"

func main()  {
    defer A()
    defer B()
    defer C()
    panic("panic A")
    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")
}
AI 代码解读
  • 该例子前面步骤和例子 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的信息
  • panic 执行 defer 中有recover
    go version:go 1.14.15
    
    AI 代码解读
package main

import "fmt"

func main()  {
    defer A()
    defer B()
    defer C()
    panic("panic A")
    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")
}
AI 代码解读
  • 该例子中前面步骤参考 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的处理流程
  • panic 执行defer中有recove&panic
    go version:go 1.14.15
    
    AI 代码解读
package main

import "fmt"

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

func A()  {
    x := recover()
    fmt.Println("func A,recover:",x)
    panic("panic R")
}

func B()  {
    panic("panic B")
    fmt.Println("func B")
}

func C(){
    fmt.Println("func C")
}
AI 代码解读
  • 该例子中前面步骤参考 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。

    7、小结

上面只是介绍如何用ChatGPT辅助学习,通过细节追问可以更加深入的了解Go语言原理,ChatGPT拥有大量的知识和信息,可以提供关于Go语言底层原理的详细解释和示例,也可以帮助我们理解底层原理的概念、工作原理和实际应用。我们也可以根据自己的兴趣和理解程度向ChatGPT提问,它支持多轮对话,和它进行交互式的学习,这种交互式学习方式可以帮助我们更好地理解和应用Go语言底层原理。

目录
打赏
0
0
0
0
1
分享
相关文章
监控局域网其他电脑:Go 语言迪杰斯特拉算法的高效应用
在信息化时代,监控局域网成为网络管理与安全防护的关键需求。本文探讨了迪杰斯特拉(Dijkstra)算法在监控局域网中的应用,通过计算最短路径优化数据传输和故障检测。文中提供了使用Go语言实现的代码例程,展示了如何高效地进行网络监控,确保局域网的稳定运行和数据安全。迪杰斯特拉算法能减少传输延迟和带宽消耗,及时发现并处理网络故障,适用于复杂网络环境下的管理和维护。
揭秘 Go 语言中空结构体的强大用法
Go 语言中的空结构体 `struct{}` 不包含任何字段,不占用内存空间。它在实际编程中有多种典型用法:1) 结合 map 实现集合(set)类型;2) 与 channel 搭配用于信号通知;3) 申请超大容量的 Slice 和 Array 以节省内存;4) 作为接口实现时明确表示不关注值。此外,需要注意的是,空结构体作为字段时可能会因内存对齐原因占用额外空间。建议将空结构体放在外层结构体的第一个字段以优化内存使用。
|
1月前
|
Go 语言入门指南:切片
Golang中的切片(Slice)是基于数组的动态序列,支持变长操作。它由指针、长度和容量三部分组成,底层引用一个连续的数组片段。切片提供灵活的增减元素功能,语法形式为`[]T`,其中T为元素类型。相比固定长度的数组,切片更常用,允许动态调整大小,并且多个切片可以共享同一底层数组。通过内置的`make`函数可创建指定长度和容量的切片。需要注意的是,切片不能直接比较,只能与`nil`比较,且空切片的长度为0。
Go 语言入门指南:切片
eino — 基于go语言的大模型应用开发框架(二)
本文介绍了如何使用Eino框架实现一个基本的LLM(大语言模型)应用。Eino中的`ChatModel`接口提供了与不同大模型服务(如OpenAI、Ollama等)交互的统一方式,支持生成完整响应、流式响应和绑定工具等功能。`Generate`方法用于生成完整的模型响应,`Stream`方法以流式方式返回结果,`BindTools`方法为模型绑定工具。此外,还介绍了通过`Option`模式配置模型参数及模板功能,支持基于前端和用户自定义的角色及Prompt。目前主要聚焦于`ChatModel`的`Generate`方法,后续将继续深入学习。
303 7
企业监控软件中 Go 语言哈希表算法的应用研究与分析
在数字化时代,企业监控软件对企业的稳定运营至关重要。哈希表(散列表)作为高效的数据结构,广泛应用于企业监控中,如设备状态管理、数据分类和缓存机制。Go 语言中的 map 实现了哈希表,能快速处理海量监控数据,确保实时准确反映设备状态,提升系统性能,助力企业实现智能化管理。
35 3
eino — 基于go语言的大模型应用开发框架(一)
Eino 是一个受开源社区优秀LLM应用开发框架(如LangChain和LlamaIndex)启发的Go语言框架,强调简洁性、可扩展性和可靠性。它提供了易于复用的组件、强大的编排框架、简洁明了的API、最佳实践集合及实用的DevOps工具,支持快速构建和部署LLM应用。Eino不仅兼容多种模型库(如OpenAI、Ollama、Ark),还提供详细的官方文档和活跃的社区支持,便于开发者上手使用。
230 8
Go 语言中的 Sync.Map 详解:并发安全的 Map 实现
`sync.Map` 是 Go 语言中用于并发安全操作的 Map 实现,适用于读多写少的场景。它通过两个底层 Map(`read` 和 `dirty`)实现读写分离,提供高效的读性能。主要方法包括 `Store`、`Load`、`Delete` 等。在大量写入时性能可能下降,需谨慎选择使用场景。
Go语言实战:错误处理和panic_recover之自定义错误类型
本文深入探讨了Go语言中的错误处理和panic/recover机制,涵盖错误处理的基本概念、自定义错误类型的定义、panic和recover的工作原理及应用场景。通过具体代码示例介绍了如何定义自定义错误类型、检查和处理错误值,并使用panic和recover处理运行时错误。文章还讨论了错误处理在实际开发中的应用,如网络编程、文件操作和并发编程,并推荐了一些学习资源。最后展望了未来Go语言在错误处理方面的优化方向。
阿里双十一背后的Go语言实践:百万QPS网关的设计与实现
解析阿里核心网关如何利用Go协程池、RingBuffer、零拷贝技术支撑亿级流量。 重点分享: ① 如何用gRPC拦截器实现熔断限流; ② Sync.Map在高并发读写中的取舍。
基于 Go 语言的公司内网管理软件哈希表算法深度解析与研究
在数字化办公中,公司内网管理软件通过哈希表算法保障信息安全与高效管理。哈希表基于键值对存储和查找,如用户登录验证、设备信息管理和文件权限控制等场景,Go语言实现的哈希表能快速验证用户信息,提升管理效率,确保网络稳定运行。
30 0

热门文章

最新文章