如何通过运行时获取真实文件名。由不同goroutine中的匿名函数调用

时间:2017-03-09 10:20:40

标签: go callstack

我有这个示例代码https://play.golang.org/p/c_2GECIcrW
我期望 getFileName 会打印出类似 main.go:11 的内容,但我得到的是 asm_amd64p32.s:1014

在这种情况下,我该怎样做才能获得预期结果? 我可以存档并仍然使用匿名函数吗?

1 个答案:

答案 0 :(得分:0)

您的期望不正确。

让我在此处粘贴您的代码以便于解释:

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返回时布尔ok设置为false,因为您正尝试访问堆栈顶部上方的内存。

如果您想知道 asm_amd64p32.s:1014 代表什么,实际上它对应于Go 1.8的goexit汇编功能(Go Playground运行最新的稳定版本Go)。尽管它的名称,goexit函数始终位于goroutine堆栈的顶部:它调用goroutine入口点函数,然后在函数返回后清理堆栈。 goexit实现是特定于体系结构的,因为它是处理goroutines堆栈细节的大多数代码。

回到你的问题,你不能指望getFileName生成的goroutine的堆栈跟踪中提到 main.go:11 ,因为主函数不在堆栈中所有。如果你真的需要打印主函数的堆栈跟踪,请不要生成goroutine。

话虽如此,还有更多值得一提的事情。 Go运行时实际上存储了有关生成goroutine的位置的其他信息。 debug.PrintStack()函数(依次基于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版本之间发生变化,因此 - 按设计 - 没有简单的方法可以通过标准库访问它们。它们是出于调试目的而提供的,作为开发人员,您不应该依赖这些信息。