首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >再谈谈获取 goroutine id 的方法

再谈谈获取 goroutine id 的方法

作者头像
李海彬
发布2018-03-27 14:16:13
2.6K0
发布2018-03-27 14:16:13
举报
去年年初的时候曾经写过一篇关于如何获取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来大致地跟踪程序并发执行的时候的状况。

package main

import (

        "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 current g

当然这个方法是内部方法,不是exported,不能被外部的调用,而且返回的数据结构也是未exported的。如果有办法暴露出这个方法,问题就解决了。

petermattis/goid 模仿runtime.getg暴露出一个getg的方法

// +build amd64 amd64p32

// +build go1.5

#include "textflag.h"

// func getg() uintptr

TEXT ·getg(SB),NOSPLIT,$0-8

        MOVQ (TLS), BX

        MOVQ BX, ret+0(FP)

        RET

上面的代码实际是将当前的goroutine的结构体的指针(TLS)返回。

参考: Golang Internals 以及中文翻译 Go语言内幕 TLS 其实是线程本地存储 (Thread Local Storage )的缩写。这个技术在很多编程语言中都有用到(请参考这里)。简单地说,它为每个线程提供了一个这样的变量,不同变量用于指向不同的内存区域。 在 Go 语言中,TLS 存储了一个 G 结构体的指针。这个指针所指向的结构体包括 Go 例程的内部细节(后面会详细谈到这些内容)。因此,当在不同的例程中访问该变量时,实际访问的是该例程相应的变量所指向的结构体。链接器知道这个变量所在的位置,前面的指令中移动到 CX 寄存器的就是这个变量。对于 AMD64,TLS 是用 FS 寄存器来实现的, 所在我们前面看到的命令实际上可以翻译为 MOVQ FS, CX。

不同的Go版本获取的数据结构可能是不同的,所以petermattis/goid针对1.5、1.6、1.9有变动的版本定制了不同的数据结构,因为我们只需要得到goroutine的ID,所以只需实现:

func Get() int64 {

        gg := (*g)(unsafe.Pointer(getg()))

        return gg.goid

}

我比较了一下#1和#2这两种实现方式的性能,差距还是非常大的:

package pkg

import (

        "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)])

        }

}

性能比较结果:

  1. BenchmarkASM-4 300000000 3.70 ns/op 0 B/op 0 allocs/op
  2. BenchmarkSlow-4 300000 4071 ns/op 1 B/op

复制代码

一千多倍的差距。

petermattis/goid这种hack的方式可以暴露更多的运行时的细节,比如我们可以扩展一下,得到当前哪个m正在运行,甚至可以得到当前的线程的信息:

type m struct {

        g0        *g

        morebuf   gobuf

        divmod    uint32

        procid    uint64

        gsignal   *g

        sigmask   sigset

        tls       [6]uintptr

        mstartfn  func()

        curg      *g

        caughtsig uintptr

        p         uintptr

        nextp     uintptr

        id        int32

}

func GetM() int32 {

        gg := (*g)(unsafe.Pointer(getg()))

        m := (*m)(unsafe.Pointer(gg.m))

        return m.id

}

sigset在不同的平台的大小是不一样的,可以参考os_*.go中各平台的定义。上面是得到m的ID, 更全的m的结构定义海包括thread等信息。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2017-08-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Golang语言社区 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档