再谈谈获取 goroutine id 的方法

  • Post author:
  • Post category:IT
  • Post comments:0评论

去年年初的时候曾经写过一篇关于如何获取goroutine id的方法:如何得到goroutine 的 id?, 当时调研了一些一些获取goid的方法。基本的方法有三种:

  1. 通过Stack信息解析出ID
  2. 通过汇编获取runtime·getg方法的调用结果
  3. 直接修改运行时的代码,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来大致地跟踪程序并发执行的时候的状况。

12345678910111213141516171819202122
package mainimport (	"log"	"time"	"github.com/petermattis/goid")func main() {	for i := 0; i < 10; i++ {		go func() {			for j := 0; j < 1000000; j++ {				log.Printf("[#%d] %d", goid.Get(), j)				time.Sleep(10e9)			}		}()	}	select {}}

依照Go代码中的文档HACKING, go运行时中实现了一个getg()方法,可以获取当前的goroutine:

getg()alone returns the currentg

当时这个方法是内部方法,不是exported,不能被外部的调用,而且返回的数据结构也是未exported的。如果有办法暴露出这个方法,问题就解决了。

petermattis/goid模仿runtime.getg暴露出一个getg的方法

https://github.com/petermattis/goid/blob/master/goid_go1.5plus.s

12345678910
// +build amd64 amd64p32// +build go1.5#include "textflag.h"// func getg() uintptrTEXT ·getg(SB),NOSPLIT,$0-8	MOVQ (TLS), BX	MOVQ BX, ret+0(FP)	RET

不同的Go版本获取的数据结构可能是不同的,所以petermattis/goid针对1.5、1.6、1.9有变动的版本定制了不同的数据结构,因为我们只需要得到goroutine的ID,所以只需实现:

1234
func Get() int64 {	gg := (*g)(unsafe.Pointer(getg()))	return gg.goid}

我比较了一下#1和#2这两种实现方式的性能,差距还是非常大的:

123456789101112131415161718192021222324
package pkgimport (	"runtime"	"testing"	"github.com/petermattis/goid")func BenchmarkASM(b *testing.B) {	b.ReportAllocs()	for i := 0; i < b.N; i++ {		goid.Get()	}}func BenchmarkSlow(b *testing.B) {	b.ReportAllocs()	var buf [64]byte	b.ResetTimer()	for i := 0; i < b.N; i++ {		goid.ExtractGID(buf[:runtime.Stack(buf[:], false)])	}}

性能比较结果:

12
BenchmarkASM-4    	300000000	         3.70 ns/op	       0 B/op	       0 allocs/opBenchmarkSlow-4   	  300000	      4071 ns/op	       1 B/op	       1 allocs/op

一千多倍的差距。

发表回复