去年年初的时候曾经写过一篇关于如何获取goroutine id的方法:如何得到goroutine 的 id?, 当时调研了一些一些获取goid的方法。基本的方法有三种:
- 通过Stack信息解析出ID
- 通过汇编获取runtime·getg方法的调用结果
- 直接修改运行时的代码,export一个可以外部调用的GoID()方法
每个方式都有些问题, #1比较慢, #2因为是hack的方式(Go team并不想暴露go id的信息), 针对不同的Go版本中需要特殊的hack手段, #3需要定制Go运行时,不通用。当时的petermattis/goid提供了 #2 的方法, 但是只能在 go 1.3中才起作用,所以只能选择#1的方式获取go id。
最近一年来, petermattis更新了他的代码,逐步增加了对 Go 1.4、1.5、1.6、1.7、1.8、1.9的支持,同时也提供了#1的方法,在#2方法不起作用的时候作为备选,所以我们可以在当前的所有的版本中可以使用stable的获取go id的方法了。
你或许会遇到一些需要使用Go ID的场景, 比如在多goroutine长时间运行任务的时候,我们通过日志来跟踪任务的执行情况,可以通过go id来大致地跟踪程序并发执行的时候的状况。
12345678910111213141516171819202122 |
package mainimport ( "log" "time" "github.com/petermattis/goid")func main() { for i := 0; i < 10; i++ { go func() { for j := 0; j < 1000000; j++ { log.Printf("[#%d] %d", goid.Get(), j) time.Sleep(10e9) } }() } select {}} |
依照Go代码中的文档HACKING, go运行时中实现了一个getg()方法,可以获取当前的goroutine:
getg()alone returns the currentg
当时这个方法是内部方法,不是exported,不能被外部的调用,而且返回的数据结构也是未exported的。如果有办法暴露出这个方法,问题就解决了。
petermattis/goid模仿runtime.getg暴露出一个getg的方法
https://github.com/petermattis/goid/blob/master/goid_go1.5plus.s
12345678910 |
// +build amd64 amd64p32// +build go1.5#include "textflag.h"// func getg() uintptrTEXT ·getg(SB),NOSPLIT,$0-8 MOVQ (TLS), BX MOVQ BX, ret+0(FP) RET |
不同的Go版本获取的数据结构可能是不同的,所以petermattis/goid针对1.5、1.6、1.9有变动的版本定制了不同的数据结构,因为我们只需要得到goroutine的ID,所以只需实现:
1234 |
func Get() int64 { gg := (*g)(unsafe.Pointer(getg())) return gg.goid} |
我比较了一下#1和#2这两种实现方式的性能,差距还是非常大的:
123456789101112131415161718192021222324 |
package pkgimport ( "runtime" "testing" "github.com/petermattis/goid")func BenchmarkASM(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { goid.Get() }}func BenchmarkSlow(b *testing.B) { b.ReportAllocs() var buf [64]byte b.ResetTimer() for i := 0; i < b.N; i++ { goid.ExtractGID(buf[:runtime.Stack(buf[:], false)]) }} |
性能比较结果:
12 |
BenchmarkASM-4 300000000 3.70 ns/op 0 B/op 0 allocs/opBenchmarkSlow-4 300000 4071 ns/op 1 B/op 1 allocs/op |
一千多倍的差距。
由 udpwork.com 聚合
|
评论: 0
|
要! 要! 即刻! Now!