首页 > 解决方案 > 将函数作为 go 例程调用会产生与 go 例程不同的调用堆栈作为匿名 func

问题描述

我有一个名为 PrintCaller() 的函数,它调用 runtime.Caller() 并跳过一帧来获取和打印调用者(PrintCaller 的)文件名和行号。这在同步运行时按预期工作,如果作为匿名函数调用异步。但是,如果只使用go关键字运行,则调用者的堆栈帧将替换为一些内部函数调用。

例如,这是函数:

func printCaller(wait chan bool) {
    _, fileName, line, _ := runtime.Caller(1)
    fmt.Printf("Filename: %s, line: %d\n", fileName, line)
}

如果我打电话是这样的:

func main() {
    printCaller()
    go func(){printCaller()}()
    go printCaller()
}

输出是:

Filename: /tmp/sandbox297971268/prog.go, line: 19
Filename: /tmp/sandbox297971268/prog.go, line: 22
Filename: /usr/local/go-faketime/src/runtime/asm_amd64.s, line: 1374

此处的工作示例:https: //play.golang.org/p/Jv21SVDY2Ln

为什么我打电话时会发生这种情况go PrintCaller(),而我打电话时却不会go func(){PrintCaller()}()?另外,有什么方法可以使这项工作使用go PrintCaller()

标签: goruntimegoroutinecallstack

解决方案


鉴于 Go 运行时系统的内部工作原理,您所看到的输出是预期的:

  • 一个 goroutine,例如main在 package中调用你自己的 goroutine,main但也包括由 启动的例程go somefunc(),实际上是某些特定于机器的启动例程中调用的。在操场上,那是src/runtime/asm_amd64.s.

  • 当你定义一个闭包时,例如:

    f := func() {
        // code
    }
    

    这会创建一个匿名函数。调用它:

    f()
    

    无论调用者是什么,都调用该匿名函数。无论闭包是分配给变量(如上f所示)还是立即调用,或者稍后使用defer,或其他任何方式,这都是正确的:

    defer func() {
        // code ...
    }()
    
  • 所以,写:

    go func() {
        // code ...
    }()
    

    只需从同一台机器特定的启动中调用匿名函数。如果该函数然后调用您的printCaller函数,该函数runtime.Caller(1)用于跳过您的printCaller函数并找到它的调用者,它会找到匿名函数:

    Filename: /tmp/sandbox297971268/prog.go, line: 22
    

    例如。

  • 但是当你写:

    go printCaller()
    

    您正在调用printCaller从特定于机器的 goroutine 启动代码命名的函数。

由于printCaller打印了它的调用者的名字,这是这个机器特定的启动代码,这就是你所看到的。

这里有一个很大的警告,那runtime.Caller就是允许失败。这就是它返回布尔值okpc uintptr, file string, line int值的原因。不能保证特定于机器的程序集调用者可以找到。


推荐阅读