去年年初的时候曾经写过一篇关于如何获取goroutine id的方法: 如何得到goroutine 的 id?, 当时调研了一些一些获取goid的方法。基本的方法有三种:
- 通过Stack信息解析出ID
- 通过汇编获取
runtime·getg
方法的调用结果 - 直接修改运行时的代码,export一个可以外部调用的GoID()方法
每个方式都有些问题, #1比较慢, #2因为是hack的方式(Go team并不想暴露go id的信息), 针对不同的Go版本中需要特殊的hack手段, #3需要定制Go运行时,不通用。当时的petermattis/goid提供了 #2 的方法, 但是只能在 go 1.3中才起作用,所以只能选择#1的方式获取go id。
最近一年来, petermattis更新了他的代码,逐步增加了对 Go 1.4、1.5、1.6、1.7、1.8、1.9的支持,同时也提供了#1的方法,在#2方法不起作用的时候作为备选,所以我们可以在当前的所有的版本中可以使用stable的获取go id的方法了。
你或许会遇到一些需要使用Go ID的场景, 比如在多goroutine长时间运行任务的时候,我们通过日志来跟踪任务的执行情况,可以通过go id来大致地跟踪程序并发执行的时候的状况。
|
|
依照Go代码中的文档HACKING, go运行时中实现了一个getg()
方法,可以获取当前的goroutine:
getg()
alone returns the currentg
当然这个方法是内部方法,不是exported,不能被外部的调用,而且返回的数据结构也是未exported的。如果有办法暴露出这个方法,问题就解决了。
petermattis/goid 模仿runtime.getg
暴露出一个getg
的方法
|
|
上面的代码实际是将当前的goroutine的结构体的指针(TLS)返回。
参考: Golang Internals 以及中文翻译 Go语言内幕
TLS 其实是线程本地存储 (Thread Local Storage )的缩写。这个技术在很多编程语言中都有用到(请参考这里)。简单地说,它为每个线程提供了一个这样的变量,不同变量用于指向不同的内存区域。在 Go 语言中,TLS 存储了一个 G 结构体的指针。这个指针所指向的结构体包括 Go 例程的内部细节(后面会详细谈到这些内容)。因此,当在不同的例程中访问该变量时,实际访问的是该例程相应的变量所指向的结构体。链接器知道这个变量所在的位置,前面的指令中移动到 CX 寄存器的就是这个变量。对于 AMD64,TLS 是用 FS 寄存器来实现的, 所在我们前面看到的命令实际上可以翻译为 MOVQ FS, CX。
不同的Go版本获取的数据结构可能是不同的,所以petermattis/goid
针对1.5、1.6、1.9有变动的版本定制了不同的数据结构,因为我们只需要得到goroutine的ID,所以只需实现:
|
|
我比较了一下#1和#2这两种实现方式的性能,差距还是非常大的:
|
|
性能比较结果:
|
|
一千多倍的差距。
petermattis/goid
这种hack的方式可以暴露更多的运行时的细节,比如我们可以扩展一下,得到当前哪个m
正在运行,甚至可以得到当前的线程的信息:
|
|
sigset
在不同的平台的大小是不一样的,可以参考os_*.go中各平台的定义。上面是得到m
的ID, 更全的m
的结构定义海包括thread
等信息。
2017-09-05 更新
petermattis 最近做了更新, 利用go本身的g_goid
宏,直接利用汇编就可以获取goroutine id,比上面的方式更简单,性能还好:
|
|