Go 獲取 Goroutine ID(goid)
在歡總的 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
參考資料