如何通过不同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 版本之间可能会发生变化,因此——按照设计——没有简单的方法可以通过标准库访问它们。它们是为调试目的而提供的,作为开发人员,您不应依赖这些信息。