再談談擷取 goroutine id 的方法

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

去年年初的時候曾經寫過一篇關於如何擷取goroutine id的方法: 如何得到goroutine 的 id?, 當時調研了一些一些擷取goid的方法。基本的方法有三種:

  1. 通過Stack資訊解析出ID
  2. 通過彙編擷取runtime·getg方法的調用結果
  3. 直接修改運行時的代碼,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 current g

當然這個方法是內部方法,不是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-8MOVQ (TLS), BXMOVQ 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,所以只需實現:

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]byteb.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

一千多倍的差距。

petermattis/goid這種hack的方式可以暴露更多的運行時的細節,比如我們可以擴充一下,得到當前哪個m正在運行,甚至可以得到當前的線程的資訊:

1234567891011121314151617181920212223
type m struct {g0        *gmorebuf   gobufdivmod    uint32procid    uint64gsignal   *gsigmask   sigsettls       [6]uintptrmstartfn  func()curg      *gcaughtsig uintptrp         uintptrnextp     uintptrid        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等資訊。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.