3.8.3 从g结构体获取goid
根据官方的Go汇编语言文档,每个运行的Goroutine结构的g指针保存在当前运行Goroutine的系统线程的局部存储TLS中。可以先获取TLS线程局部存储,然后再从TLS中获取g结构的指针,最后从g结构中取出goid。
下面是参考runtime包中定义的get_tls宏获取g指针:
get_tls(CX)
MOVQ g(CX), AX // Move g into AX.
其中get_tls是一个宏函数,在 runtime/go_tls.h 头文件中定义。
对于AMD64平台,get_tls宏函数定义如下:
#ifdef GOARCH_amd64
#define get_tls(r) MOVQ TLS, r
#define g(r) 0(r)(TLS*1)
#endif
将get_tls宏函数展开之后,获取g指针的代码如下:
MOVQ TLS, CX
MOVQ 0(CX)(TLS*1), AX
其实TLS类似线程局部存储的地址,地址对应的内存里的数据才是g指针。我们还可以更直接一点:
MOVQ (TLS), AX
基于上述方法可以包装一个getg函数,用于获取g指针:
// func getg() unsafe.Pointer
TEXT ·getg(SB), NOSPLIT, $0-8
MOVQ (TLS), AX
MOVQ AX, ret+0(FP)
RET
然后在Go代码中通过goid成员在g结构体中的偏移量来获取goid的值:
const g_goid_offset = 152 // Go1.10
func GetGroutineId() int64 {
g := getg()
p := (*int64)(unsafe.Pointer(uintptr(g) + g_goid_offset))
return *p
}
其中 g_goid_offset
是 goid 成员的偏移量,g 结构参考 runtime/runtime2.go。
在Go1.10版本,goid的偏移量是152字节。因此上述代码只能正确运行在goid偏移量也是152字节的Go版本中。根据汤普森大神的神谕,枚举和暴力穷举是解决一切疑难杂症的万金油。我们也可以将goid的偏移保存到表格中,然后根据Go版本号查询goid的偏移量。
下面是改进后的代码:
var offsetDictMap = map[string]int64{
"go1.10": 152,
"go1.9": 152,
"go1.8": 192,
}
var g_goid_offset = func() int64 {
goversion := runtime.Version()
for key, off := range offsetDictMap {
if goversion == key || strings.HasPrefix(goversion, key) {
return off
}
}
panic("unsupport go verion:"+goversion)
}()
现在的goid偏移量已经终于可以自动适配已经发布的Go语言版本。
学员评价