您當前的位置:首頁 > 動漫

Go 獲取 Goroutine ID(goid)

作者:由 陶文 發表于 動漫時間:2018-02-04

在歡總的 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

參考資料

標簽: reflect  elem  stack  Typ  runtime