如何得到goroutine 的 id?

使用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:

  1. 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)
  2. 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。代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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输出:

1
2
3
4
5
6
7
8
9
10
11
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的时候才用它。

参考资料

  1. https://groups.google.com/forum/#!topic/golang-nuts/Nt0hVV_nqHE
  2. http://wendal.net/2013/0205.html
  3. http://blog.sgmansfield.com/2015/12/goroutine-ids/
  4. http://dave.cheney.net/2013/09/07/how-to-include-c-code-in-your-go-package
  5. http://golanghome.com/post/566
  6. https://github.com/t-yuki/goid
  7. https://github.com/petermattis/goid.git
  8. https://github.com/huandu/goroutine