Go问答 如何得到 goroutine 的 id

astaxie · 2020年02月12日 · 157 次阅读

使用 Java 的时候很容易得到线程的名字, 比如"Thread.currentThread().getName",这样就可以进行一些监控操作或者设置线程相关的一些数据。当转向 Golang 开发的时候,却发现 Go 语言并没有提供获取当前 goroutine id 的操作。这是 Golang 的开发者故意为之,避免开发者滥用 goroutine id 实现 goroutine local storage (类似 java 的"thread-local" storage), 因为 goroutine local storage 很难进行垃圾回收。因此尽管以前暴露出了相应的方法,现在已经把它隐藏了。

Please don't use goroutine local storage. It's highly discouraged. In fact, IIRC, we used to expose Goid, but it is hidden since we don't want people to do this.

Potential problems include:

when goroutine goes away, its goroutine local storage won't be GCed. (you can get goid for the current goroutine, but you can't get a list of all running goroutines) what if handler spawns goroutine itself? the new goroutine suddenly loses access to your goroutine local storage. You can guarantee that your own code won't spawn other goroutines, but in general you can't make sure the standard library or any 3rd party code won't do that. thread local storage is invented to help reuse bad/legacy code that assumes global state, Go doesn't have legacy code like that, and you really should design your code so that state is passed explicitly and not as global (e.g. resort to goroutine local storage)

当然 Go 的这种隐藏的做法还是有争议的,有点因噎废食。在 debug log 的时候 goroutine id 是很好的一个监控信息。本文介绍了两种获取 goroutine id 的方法。

可以用纯 Go 语言获取当前的 goroutine id。代码如下所示:

package main
import (
    "fmt"
    "runtime"
    "strconv"
    "strings"
    "sync"
)
func GoID() int {
    var buf [64]byte
    n := runtime.Stack(buf[:], false)
    idField := strings.Fields(strings.TrimPrefix(string(buf[:n]), "goroutine "))[0]
    id, err := strconv.Atoi(idField)
    if err != nil {
        panic(fmt.Sprintf("cannot get goroutine id: %v", err))
    }
    return id
}
func main() {
    fmt.Println("main", GoID())
    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
        i := i
        wg.Add(1)
        go func() {
            defer wg.Done()
            fmt.Println(i, GoID())
        }()
    }
    wg.Wait()
}

go run main.go 输出:

main 1
9 14
0 5
1 6
2 7
5 10
6 11
3 8
7 12
4 9
8 13

它利用 runtime.Stack 的堆栈信息。runtime.Stack(buf []byte, all bool) int会将当前的堆栈信息写入到一个 slice 中,堆栈的第一行为 goroutine #### […,其中 #### 就是当前的 gororutine id,通过这个花招就实现 GoID 方法了。

但是需要注意的是,获取堆栈信息会影响性能,所以建议你在 debug 的时候才用它。

更多原创文章干货分享,请关注公众号
  • 加微信实战群请加微信(注明:实战群):gocnio
暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册