Go 获取 Goroutine ID(goid)

陶文 · · 3818 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

在欢总的 huandu/goroutine 指引下,我们知道了如何获取 goroutine id。简单概述一下目前的方式。首先是 goid 是当前 goroutine 的编号,存在于一个叫 g 的结构体上。这个结构体定义在 runtime 包里,是私有的。目前的版本 g struct 长这个样子

type g struct {
	// Stack parameters.
	// stack describes the actual stack memory: [stack.lo, stack.hi).
	// stackguard0 is the stack pointer compared in the Go stack growth prologue.
	// It is stack.lo+StackGuard normally, but can be StackPreempt to trigger a preemption.
	// stackguard1 is the stack pointer compared in the C stack growth prologue.
	// It is stack.lo+StackGuard on g0 and gsignal stacks.
	// It is ~0 on other goroutine stacks, to trigger a call to morestackc (and crash).
	stack       stack   // offset known to runtime/cgo
	stackguard0 uintptr // offset known to liblink
	stackguard1 uintptr // offset known to liblink

	_panic         *_panic // innermost panic - offset known to liblink
	_defer         *_defer // innermost defer
	m              *m      // current m; offset known to arm liblink
	sched          gobuf
	syscallsp      uintptr        // if status==Gsyscall, syscallsp = sched.sp to use during gc
	syscallpc      uintptr        // if status==Gsyscall, syscallpc = sched.pc to use during gc
	stktopsp       uintptr        // expected sp at top of stack, to check in traceback
	param          unsafe.Pointer // passed parameter on wakeup
	atomicstatus   uint32
	stackLock      uint32 // sigprof/scang lock; TODO: fold in to atomicstatus
	goid           int64
	waitsince      int64  // approx time when the g become blocked
	waitreason     string // if status==Gwaiting
	schedlink      guintptr
	preempt        bool     // preemption signal, duplicates stackguard0 = stackpreempt
	paniconfault   bool     // panic (instead of crash) on unexpected fault address
	preemptscan    bool     // preempted g does scan for gc
	gcscandone     bool     // g has scanned stack; protected by _Gscan bit in status
	gcscanvalid    bool     // false at start of gc cycle, true if G has not run since last scan; TODO: remove?
	throwsplit     bool     // must not split stack
	raceignore     int8     // ignore race detection events
	sysblocktraced bool     // StartTrace has emitted EvGoInSyscall about this goroutine
	sysexitticks   int64    // cputicks when syscall has returned (for tracing)
	traceseq       uint64   // trace event sequencer
	tracelastp     puintptr // last P emitted an event for this goroutine
	lockedm        muintptr
	sig            uint32
	writebuf       []byte
	sigcode0       uintptr
	sigcode1       uintptr
	sigpc          uintptr
	gopc           uintptr // pc of go statement that created this goroutine
	startpc        uintptr // pc of goroutine function
	racectx        uintptr
	waiting        *sudog         // sudog structures this g is waiting on (that have a valid elem ptr); in lock order
	cgoCtxt        []uintptr      // cgo traceback context
	labels         unsafe.Pointer // profiler labels
	timer          *timer         // cached timer for time.Sleep
	selectDone     uint32         // are we participating in a select and did someone win the race?

	// Per-G GC state

	// gcAssistBytes is this G's GC assist credit in terms of
	// bytes allocated. If this is positive, then the G has credit
	// to allocate gcAssistBytes bytes without assisting. If this
	// is negative, then the G must correct this by performing
	// scan work. We track this in bytes to make it fast to update
	// and check for debt in the malloc hot path. The assist ratio
	// determines how this corresponds to scan work debt.
	gcAssistBytes int64
}

每个 goroutine 都有这样的一个 g 的结构体的实例,它的指针放在一个叫 TLS 的伪寄存器里面。获取 *g 的汇编代码

MOVQ TLS, CX
MOVQ 0(CX)(TLS*1), AX
MOVQ AX, ret+0(FP)

编写一个 getg 的实现是很容易的,用 Go 的汇编,给不同的几个架构写一个对应的实现就可以了。接下来的问题是如何在 g 的指针上获取其 goid 这个成员变量。问题的难度在于不同的 Go 版本的偏移量是不一样的。

所以 huandu/goroutine 实现的办法就是把各个版本的 g 的结构体定义都拷贝一份出来。然后运行时去判断 Go 的版本号,把指针转成对应版本的结构体的指针。

这里我们提供一种另外的办法

像 Class.forName 一样用名字获取 reflect.Type

在 Java 里,可以很容易地用 class 的名字获得这个 class 的反射对象。但是在 Go 里面并没有提供这样的选项。所以我们要获得 runtime.g 的定义,先要实现一个 Go 版本的 Class.forName

//+build go1.5

package gls

import (
	"unsafe"
	"reflect"
	"runtime"
	"strings"
)

// typelinks1 for 1.5 ~ 1.6
//go:linkname typelinks1 reflect.typelinks
func typelinks1() [][]unsafe.Pointer

// typelinks2 for 1.7 ~
//go:linkname typelinks2 reflect.typelinks
func typelinks2() (sections []unsafe.Pointer, offset [][]int32)

var types = map[string]reflect.Type{}

func init() {
	ver := runtime.Version()
	if ver == "go1.5" || strings.HasPrefix(ver, "go1.5.") {
		loadGo15Types()
	} else if ver == "go1.6" || strings.HasPrefix(ver, "go1.6.") {
		loadGo15Types()
	} else {
		loadGo17Types()
	}
	gType := TypeForName("runtime.g")
	if gType == nil {
		panic("failed to get runtime.g type")
	}
	goidField, found := gType.FieldByName("goid")
	if !found {
		panic("failed to get goid from runtime.g type")
	}
	goidOffset = goidField.Offset
}

func loadGo15Types() {
	var obj interface{} = reflect.TypeOf(0)
	typePtrss := typelinks1()
	for _, typePtrs := range typePtrss {
		for _, typePtr := range typePtrs {
			(*emptyInterface)(unsafe.Pointer(&obj)).word = typePtr
			typ := obj.(reflect.Type)
			if typ.Kind() == reflect.Ptr && typ.Elem().Kind() == reflect.Struct {
				types[typ.Elem().String()] = typ.Elem()
			}
			if typ.Kind() == reflect.Slice && typ.Elem().Kind() == reflect.Ptr &&
				typ.Elem().Elem().Kind() == reflect.Struct {
				types[typ.Elem().Elem().String()] = typ.Elem().Elem()
			}
		}
	}
}

func loadGo17Types() {
	var obj interface{} = reflect.TypeOf(0)
	sections, offset := typelinks2()
	for i, offs := range offset {
		rodata := sections[i]
		for _, off := range offs {
			(*emptyInterface)(unsafe.Pointer(&obj)).word = resolveTypeOff(unsafe.Pointer(rodata), off)
			typ := obj.(reflect.Type)
			if typ.Kind() == reflect.Ptr && typ.Elem().Kind() == reflect.Struct {
				types[typ.Elem().String()] = typ.Elem()
			}
		}
	}
}

type emptyInterface struct {
	typ  unsafe.Pointer
	word unsafe.Pointer
}

// TypeForName return the type by its name, just like Class.forName in java
func TypeForName(typeName string) reflect.Type {
	return types[typeName]
}

其实利用 reflect.typelinks() 这个函数是可以枚举所有的类型的。但是这个函数是 reflect 包私有的。所以我们先要用 go:linkname 把这个私有函数链接进来。

然后我们获得的类型是用 reflect.rtype 的指针来表示的。我们还要把这个具体类型的指针转成 reflect.Type 这样的接口。这里使用的办法是先用 interface{} 包含一个 *rtype。然后把 interface{} 里的指针给换掉。然后这个 interface{} 转成 reflect.Type。

拿到了 runtime.g 的 reflect.Type,获取 goid 的偏移量,直接使用反射的api,去取 field 的 offset 就可以了。

有 goid,我们就可以实现一个类似 tls 的 gls 了: v2pro/plz

参考资料

How to call private functions (bind to hidden symbols) in GoLangAdventures in Go: Accessing Unexported Functionsspance/go-callprivate


有疑问加站长微信联系(非本文作者)

本文来自:知乎专栏

感谢作者:陶文

查看原文:Go 获取 Goroutine ID(goid)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

3818 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传