How to get real file name by runtime.Caller called by anonymous function in different goroutine

huangapple go评论89阅读模式
英文:

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

在这种情况下,我该怎么做才能得到期望的结果?
我能实现这个并且仍然使用匿名函数吗?

英文:

I have this example code https://play.golang.org/p/c_2GECIcrW
What I expect getFileName will print out something like main.go:11 but what I get is the asm_amd64p32.s:1014

What can I do to get the expect result in this case?
Can I archive that and still using anonymous function?

答案1

得分: 2

你的期望是错误的。

让我把你的代码粘贴在这里以便更容易解释:

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堆栈的顶部,
  • 对于任何x > 1的runtime.Caller(x),返回的布尔值ok设置为false,因为你试图访问超出堆栈顶部的内存。

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

回到你的问题,你不能期望在由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版本之间可能会发生变化,标准库中没有简单的方法来访问它们。它们提供给调试目的,作为开发人员,你不应该依赖这些信息。

英文:

Your expectation is incorrect.

Let me paste your code here for ease of explanation:

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)
	}()
}

Your anonymous function is running in a goroutine spawned by getFileName.

Each goroutine executes using its own call stack, even the goroutine spawned by getFileName starts with a fresh stack.

By invoking runtime.Caller(skip int) with a skip value greater than zero, you can walk through the stack frames of the current goroutine.

In your example:

  • runtime.Caller(0) prints main.go:17 because this is the line where runtime.Caller() is actually called,
  • runtime.Caller(1) prints asm_amd64p32.s:1014 because it is the top of the stack of the current goroutine,
  • runtime.Caller(x) for any x > 1 returns with the boolean ok set to false because you're trying to access memory above the top of the stack.

If you're wondering what asm_amd64p32.s:1014 represents, actually it corresponds to the goexit assembly function of Go 1.8 (the Go Playground runs with the latest stable release of Go). Despite its name, the goexit function is always at the top of a goroutine stack: it invokes the goroutine entry point function and then clean up the stack after the function returns. The goexit implementation is architecture-specific, as it is most of the code that handles the details of the goroutines stack.

Back to your question, you cannot expect to see main.go:11 mentioned in the stacktrace of the goroutine spawned by getFileName, because the main function is not in the stack at all. Don't spawn a goroutine, if you really need to print the stacktrace of the main function.

Having said that, there is something more to mention. The Go runtime actually stores additional information about where a goroutine has been spawned. The debug.PrintStack() function (which is in turn based on runtime.Stack()) is able to print it out in a nicely formatted stacktrace:

package main

import (
	"time"
	"runtime/debug"
)

func main() {
	getFileName(1)
	time.Sleep(time.Hour)
}

func getFileName(shift int) {
	go func() {
		debug.PrintStack()
	}()
}

Output:

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

Being all these details implementation-dependent and subject to change between different Go releases, there is - by design - no easy way to access them via the standard library. They're provided for debugging purposes and you, as a developer, should not rely on these information.

huangapple
  • 本文由 发表于 2017年3月9日 18:20:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/42692654.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定