我们常用的C/C++,java等语言,原生的对ThreadID提供了访问的能力,但是Go语言并没有。线程ID可以与线程本地存储(TLS)搭配使用,可以产生一些新的玩法。线程ID可以用于分布式锁的条件,即让特定的线程对特定的资源上锁或解锁。
Go语言虽然未向外提供协程(Go语言本身的并发单元是协程)ID的获取方式。但并不代表它没有协程ID的概念,也不代表我们获取不到协程ID。
如下将提供几种获取goroutineID的办法。
方式1,修改Go源码,封装getg()
该方式对Go源码具有侵入性,不建议使用。该方式也不利于基础库相关组件的维护。
func GetGoID1() uint64 {
return getg().goid
}
方式2,在运行时栈中查找
该方式打印与Copy运行时栈信息,会产生一定开销,速度有限,但对源码无侵入性。
func GetGoID2() int64 {
b := make([]byte, 64)
b = b[:runtime.Stack(b, false)]
var id int64
fmt.Sscanf(string(b), "goroutine %d", &id)
return id
}
方式3,通过debug库获取栈信息,同时扫描goroutineID,该方式弊端同方式2
func GetGoID3() uint64 {
stack := debug.Stack()
var id uint64
fmt.Sscanf(string(stack[:64]), "goroutine %d", &id)
return id
}
方式4,采用Go汇编的方式
此方式是根据GMP模型中g结构的字段偏移,获取goroutineID。
// 通过汇编指令,映射出goid,见曹春晖的分享
// copy from https://github.com/cch123/goroutineid
var offsetDict = map[string]int64{
"go1.4": 128,
"go1.5": 184,
"go1.6": 192,
"go1.7": 192,
"go1.8": 192,
"go1.9": 152,
"go1.10": 152,
"go1.11": 152,
"go1.12": 152,
"go1.13": 152,
"go1.14": 152,
//...
"go1.20": 152,
}
var offset = func() int64 {
ver := strings.Join(strings.Split(runtime.Version(), ".")[:2], ".")
return offsetDict[ver]
}()
// GetGoID returns the goroutine id
func GetGoID4() int64
go_tls.h
#ifdef GOARCH_arm
#define LR R14
#endif
#ifdef GOARCH_amd64
#define get_tls(r) MOVQ TLS, r
#define g(r) 0(r)(TLS*1)
#endif
#ifdef GOARCH_amd64p32
#define get_tls(r) MOVL TLS, r
#define g(r) 0(r)(TLS*1)
#endif
#ifdef GOARCH_386
#define get_tls(r) MOVL TLS, r
#define g(r) 0(r)(TLS*1)
#endif
goid.s
#include "textflag.h"
#include "go_tls.h"
// func GetGoID4() int64
TEXT ·GetGoID4(SB), NOSPLIT, $0-8
get_tls(CX)
MOVQ g(CX), AX
MOVQ ·offset(SB), BX
LEAQ 0(AX)(BX*1), DX
MOVQ (DX), AX
MOVQ AX, ret+0(FP)
RET
Reference
1.https://github.com/cch123/asmshare/blob/master/layout.md
3.https://chai2010.cn/advanced-go-programming-book/ch3-asm/ch3-01-basic.html