如何通过不同goroutine中匿名函数调用的runtime.Caller获取真实文件名
How to get real file name by runtime.Caller called by anonymous function in different goroutine
我有这个示例代码https://play.golang.org/p/c_2GECIcrW
我期望 getFileName 会打印出类似 main.go:11 但我得到的是 asm_amd64p32.s:1014
在这种情况下,我该怎么做才能得到预期的结果?
我可以存档并继续使用匿名函数吗?
您的期望不正确。
为了便于解释,让我将您的代码粘贴到此处:
package main
import (
"fmt"
"path/filepath"
"runtime"
"time"
)
func main() {
getFileName(1)
time.Sleep(time.Hour)
}
func getFileName(shift int) {
go func() {
_, file, line, ok := runtime.Caller(shift)
if !ok {
file = "???"
line = 0
} else {
file = filepath.Base(file)
}
fmt.Printf("%s:%d", file, line)
}()
}
您的匿名函数是 运行 在由 getFileName
生成的 goroutine 中。
每个 goroutine 都使用自己的调用堆栈执行,即使 getFileName
生成的 goroutine 也是从一个新堆栈开始的。
通过使用大于零的跳过值调用runtime.Caller(skip int)
,您可以遍历当前 goroutine 的堆栈帧。
在你的例子中:
runtime.Caller(0)
打印 main.go:17 因为这是实际调用 runtime.Caller()
的行,
runtime.Caller(1)
打印 asm_amd64p32.s:1014 因为它是当前 goroutine 的栈顶,
runtime.Caller(x)
对于任何 x > 1 returns 并且布尔值 ok
设置为 false
因为您正在尝试访问堆栈顶部以上的内存。
如果你想知道 asm_amd64p32.s:1014 代表什么,实际上它对应于 Go 1.8 的 goexit 汇编函数(Go Playground 运行Go 的最新稳定版本)。尽管它的名称如此,goexit
函数始终位于 goroutine 堆栈的顶部:它调用 goroutine 入口点函数,然后在函数 returns 之后清理堆栈。 goexit
实现是特定于体系结构的,因为它是处理 goroutines 堆栈细节的大部分代码。
回到你的问题,你不能指望在 getFileName
生成的 goroutine 的堆栈跟踪中看到 main.go:11,因为主要函数根本不在堆栈中。如果您确实需要打印主函数的堆栈跟踪,请不要生成 goroutine。
说到这里,还有话要说。 Go 运行时实际上存储了有关生成 goroutine 的位置的附加信息。 debug.PrintStack() function (which is in turn based on runtime.Stack()) 能够以格式良好的堆栈跟踪打印出来:
package main
import (
"time"
"runtime/debug"
)
func main() {
getFileName(1)
time.Sleep(time.Hour)
}
func getFileName(shift int) {
go func() {
debug.PrintStack()
}()
}
输出:
goroutine 5 [running]:
runtime/debug.Stack(0x0, 0x0, 0x0, 0x0)
/usr/local/go/src/runtime/debug/stack.go:24 +0x80
runtime/debug.PrintStack()
/usr/local/go/src/runtime/debug/stack.go:16 +0x20
main.getFileName.func1()
/tmp/sandbox085104368/main.go:15 +0x20
created by main.getFileName
/tmp/sandbox085104368/main.go:16 +0x40
由于所有这些细节都依赖于实现,并且在不同的 Go 版本之间可能会发生变化,因此——按照设计——没有简单的方法可以通过标准库访问它们。它们是为调试目的而提供的,作为开发人员,您不应依赖这些信息。
我有这个示例代码https://play.golang.org/p/c_2GECIcrW
我期望 getFileName 会打印出类似 main.go:11 但我得到的是 asm_amd64p32.s:1014
在这种情况下,我该怎么做才能得到预期的结果?
我可以存档并继续使用匿名函数吗?
您的期望不正确。
为了便于解释,让我将您的代码粘贴到此处:
package main
import (
"fmt"
"path/filepath"
"runtime"
"time"
)
func main() {
getFileName(1)
time.Sleep(time.Hour)
}
func getFileName(shift int) {
go func() {
_, file, line, ok := runtime.Caller(shift)
if !ok {
file = "???"
line = 0
} else {
file = filepath.Base(file)
}
fmt.Printf("%s:%d", file, line)
}()
}
您的匿名函数是 运行 在由 getFileName
生成的 goroutine 中。
每个 goroutine 都使用自己的调用堆栈执行,即使 getFileName
生成的 goroutine 也是从一个新堆栈开始的。
通过使用大于零的跳过值调用runtime.Caller(skip int)
,您可以遍历当前 goroutine 的堆栈帧。
在你的例子中:
runtime.Caller(0)
打印 main.go:17 因为这是实际调用runtime.Caller()
的行,runtime.Caller(1)
打印 asm_amd64p32.s:1014 因为它是当前 goroutine 的栈顶,runtime.Caller(x)
对于任何 x > 1 returns 并且布尔值ok
设置为false
因为您正在尝试访问堆栈顶部以上的内存。
如果你想知道 asm_amd64p32.s:1014 代表什么,实际上它对应于 Go 1.8 的 goexit 汇编函数(Go Playground 运行Go 的最新稳定版本)。尽管它的名称如此,goexit
函数始终位于 goroutine 堆栈的顶部:它调用 goroutine 入口点函数,然后在函数 returns 之后清理堆栈。 goexit
实现是特定于体系结构的,因为它是处理 goroutines 堆栈细节的大部分代码。
回到你的问题,你不能指望在 getFileName
生成的 goroutine 的堆栈跟踪中看到 main.go:11,因为主要函数根本不在堆栈中。如果您确实需要打印主函数的堆栈跟踪,请不要生成 goroutine。
说到这里,还有话要说。 Go 运行时实际上存储了有关生成 goroutine 的位置的附加信息。 debug.PrintStack() function (which is in turn based on runtime.Stack()) 能够以格式良好的堆栈跟踪打印出来:
package main
import (
"time"
"runtime/debug"
)
func main() {
getFileName(1)
time.Sleep(time.Hour)
}
func getFileName(shift int) {
go func() {
debug.PrintStack()
}()
}
输出:
goroutine 5 [running]:
runtime/debug.Stack(0x0, 0x0, 0x0, 0x0)
/usr/local/go/src/runtime/debug/stack.go:24 +0x80
runtime/debug.PrintStack()
/usr/local/go/src/runtime/debug/stack.go:16 +0x20
main.getFileName.func1()
/tmp/sandbox085104368/main.go:15 +0x20
created by main.getFileName
/tmp/sandbox085104368/main.go:16 +0x40
由于所有这些细节都依赖于实现,并且在不同的 Go 版本之间可能会发生变化,因此——按照设计——没有简单的方法可以通过标准库访问它们。它们是为调试目的而提供的,作为开发人员,您不应依赖这些信息。