将函数作为 go routine 调用会产生与 go routine 不同的调用堆栈作为匿名 func

Calling a function as a go routine produces different call stack from go routine as anonymous 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() 来完成这项工作吗?

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

  • 一个goroutine,比如在package main中调用你自己的main的主goroutine,还包括go somefunc()启动的routines,实际上是调用来自 一些特定于机器的启动例程。在 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()
    

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

由于 printCaller 打印其调用者的名称,即此机器特定的启动代码,这就是您所看到的。

这里有一个很大的警告,那就是 runtime.Caller 允许失败。这就是为什么它 returns 一个布尔值 ok 以及 pc uintptr, file string, line int 值。不能保证可以找到特定于机器的程序集调用程序。