27|即学即练:跟踪函数调用链,理解代码更直观
该思维导图由 AI 生成,仅供参考
引子
- 深入了解
- 翻译
- 解释
- 总结
文章介绍了如何使用defer函数来跟踪函数调用链,并解决相关问题。通过具体的代码示例和问题解决过程,帮助读者更好地理解了如何使用defer函数来实现函数执行过程的跟踪。作者提出了一些问题,并逐一分析并解决了这些问题,如手动显式传入要跟踪的函数名、并发应用中函数链跟踪混在一起无法分辨等。通过借助Go标准库runtime包,实现了自动获取所跟踪函数的函数名,解决了手动显式传入函数名的问题。随后,文章进一步讨论了如何支持多Goroutine函数调用链的跟踪,并实现了输出带有Goroutine ID的函数跟踪信息。最终,通过增加缩进层次信息,实现了输出具有层次感的函数调用跟踪信息。整体而言,本文通过具体的代码示例和问题解决过程,帮助读者更好地理解了如何使用defer函数来跟踪函数调用链,以及如何解决相关问题。文章还介绍了如何利用instrument工具自动注入跟踪代码,为读者提供了实际操作的示例。
《Tony Bai · Go 语言第一课》,新⼈⾸单¥59
全部留言(29)
- 最新
- 精选
- Darren老师,问个小白的问题哈,就是Java和Python都支持注解增加能力,不会修改源代码。 我看您这节课的最终版本,就是工具修改源代码,那么go有没有类似Java和Python那种注解的增强能力?如果没有,那么是因为什么原因不支持呀?
作者回复: go原生不支持注解功能。官方对此原因没有任何说明。go支持struct tag,一定程度具备了annotation的性质。
2021-12-2449 - 路边的猪var mgroup = make(map[uint64]int) var mutex sync.Mutex func Trace() func() { pc, _, _, ok := runtime.Caller(1) if !ok { fmt.Println("报错") } funcccc := runtime.FuncForPC(pc) funname := funcccc.Name() gid := curGoroutineID() mutex.Lock() index := mgroup[gid] mgroup[gid] = index + 1 mutex.Unlock() s := "" for i := 0; i <= index; i++ { s = s + " " } fmt.Printf("g[%05d]:%s-> enter:%s\n", gid, s, funname) return func() { fmt.Printf("g[%05d]:%s<- exit :%s\n", gid, s, funname) } } 利用defer后面的表达式在入栈时求值这一特性,用一个缩紧变量就行了,闭包中的 indents -1 有点多此一举吧?
作者回复: 嗯,不错的思路。
2022-05-104 - 木木感谢,这节课觉得学到了很多。有个问题,在文中演示如何获得 Goroutine ID的trace例程里,waitGroup的作用是什么?我本来以为是像信号量一样的同步手段,但是想了一想发现并不是,因为wait在A1()之后。如果wait在A1()之前的话,可以保证让A2先执行完再执行A1。文中这种在A1()之后wait()的原因是什么?
作者回复: waitgroup是go标准库sync包提供的一个功能特性,常用用于等待一组子goroutine的退出。可以看看go官方相关文档以及文档中的用法。
2021-12-2334 - Geralt思考题的一个思路: 在instrument_trace目录下新建一个config目录,里面有dev.go和prod.go两个文件: dev.go //go:build dev package config const ShouldPrint = true ------ prod.go //go:build prod package config const ShouldPrint = false ------ 修改Trace()函数,在方法体内先判断ShouldPrint的值,若为false则返回一个空的匿名函数。 通过go build -tags dev(prod) 可以指定config目录下哪个文件参与编译。
作者回复: 都用build tag了,应该就不需要shouldprint了吧。
2021-12-2324 - 张申傲就喜欢这种实战,可以把前面的知识点都串起来,对于加深理解很有帮助~
作者回复: 👍
2021-12-224 - KingOfDark1. 对于go build的编译过程,有点疑问,就比如这里编译 go build demo.go ,会把依赖的包也都给重新编译吗? 还是说依赖包的都是提前编译好的(或者说只会有一次编译,之后不会重新编译了,只需要在链接即可?) 2. 对于思考题的使用build tags,有两种思路: 第一种思路,是 trace.go 有两个版本(文件名可以分别为 dev_trace.go, prod_trace.go),dev 版本的trace 是正常的打印逻辑,prod 版本直接返回空函数体 第二种思路,是 要编译/追踪的go源文件有两个版本,一个带有trace函数,一个不带trace函数(这个方法好像用不到 build tag 了,但是这样好像把defer的开销也省去了)
作者回复: 1. 关于go增量编译,可以了解一下 https://tonybai.com/2022/03/21/go-native-support-incremental-build 2. 第一个思路✅。第二种思路维护起来过于麻烦了。
2022-06-1523 - 木木一个问题:老师代码里好几处用到了类似 fd, ok :=decl.(*ast.FuncDecl) 这种写法,看了一下,ast是package,FuncDecl是一个struct,decl是一个ast.Decl类型的变量,给我搞晕了。请问等号右边的意思是什么?
作者回复: 这不能怪你,因为这里使用了接口的类型断言(type assert)语法,可以先看看第28讲后,再回来看这段代码。
2021-12-2323 - qinsi一些疑问: 1. 在输出带缩进的跟踪信息时,用一个map保存了不同goroutine的当前缩进。但似乎每个goroutine都只会访问自己的id对应的kv,不存在不同的goroutine访问同一个key的情况。这种情况下能否不加锁呢? 2. 在其他语言的生态中,实现无侵入的链路跟踪通常都是在语言的中间表示上做文章,比如JVM字节码或是LLVM IR。查了下go似乎也有自己的一种ssa ir,那么是否有可能也在这种ir上做做文章?
作者回复: 1. go的map类型如果发现多个goroutine尝试对其进行写操作,但没有加锁,就可能抛出panic 2. 思路不错,但我对ssa ir了解不多,如果你对ssa ir很了解,建议你尝试一下,有成果后也可以分享出来。
2021-12-2273 - Geek_as老师,我觉得那个map加锁好像不需要,map的确是不支持并发写,但我觉得这个并发写,应该是不支持多个gorunine对同一个key写,但是现在这个项目,每个gorunine是对属于自己的key进行操作,即每个key任何时刻最多只会被一个gorunine写,不存在并发问题
作者回复: runtime层面对map并发读写的检测是整体的,不会考虑goroutine是否各自访问自己的数据。
2022-04-262 - lesserror感谢大白老师,这一讲的内容很有启发性。有几个小疑问,劳烦有时间回复一下: 1. 文中说:“于是当 foo 函数返回后,这个闭包函数就会被执行。” 我想的是,这里是不是 foo 函数返回之前,闭包函数就会被执行呢? foo 函数返回后,是不是代表这个函数已经执行完毕了? 2. 第一个返回值代表的是程序计数(pc)。我打印pc变量,出来的是类似: 17343465、17343241、17343369······,这个计数究竟是什么呢,内存地址吗? 3. 文中的这两步操作:$go build github.com/bigwhite/instrument_trace/cmd/instrument $instrument version 我的理解是编译生成了可执行二进制文件后,需要放到 类似 bin目录中,才能全局 执行 “instrument version” 命令吧? 感觉老师这里还少了一步操作。 4. 不建议使用 Goroutine ID的最大原因是什么? 文中链接中的讨论组内容没有仔细看完。 5. 课后问题的比较优越的实现方案是什么?想听到老师的答案。 ps:问题有点多,但是确实属于我这节课看完后的疑惑,谢谢老师解答。
作者回复: 1. 这里所谓的foo函数返回后,指的是defer函数被执行,deferred函数即是那个闭包函数。 2.pc是程序计数器,冯 ·诺伊曼计算机体系结构中的一个寄存器。可以自行google或baidu一下。 3. go build后,instrument程序会出现在当前目录下。 4. 最大原因还是避免被滥用。避免写出强依赖goroutine id的代码。因为强依赖goroutine将导致代码不好移植,同时也会导致并发模型复杂化。 5. 提示里有,使用build tag。关于build tag用法,可以参考go官方文档。
2021-12-242