Go語言如何獲取goroutine的id
如果你使用過如 Python、Java 等主流支持并發(fā)的編程語言,那么通常都能夠比較容易的獲得進程和線程的 id。但是在 Go 語言,沒有直接提供對多進程和多線程的支持,而是提供了 goroutine 來支持并發(fā)編程。不過在 Go 中,獲取 goroutine 的 id 并不像其他編程語言那樣容易,但依然有辦法,本文就來介紹下如何實現(xiàn)。
獲取當前進程的 id
首先,雖然 Go 沒有提供多進程編程,但啟動 Go 程序還是會有一個進程存在的,Go 標準庫提供了 os.Getpid 函數(shù),可以方便的獲取當前進程的 id:
github.com/jianghushinian/blog-go-example/blob/main/goroutine/id/pid/main.go
package main
import (
"fmt"
"os"
)
func main() {
// 獲取當前進程的 id
pid := os.Getpid()
fmt.Println("process id:", pid)
}
調用 os.Getpid() 會返回當前進程的 pid(進程 id)。
獲取當前 goroutine 的 id
Go 并沒有直接提供獲取 goroutine id 的方法,因為 goroutine 的管理是由 Go 運行時(Go runtime)負責的,它并不暴露每個 goroutine 的 id。然而,有一些方法可以間接獲取到與 goroutine 相關的信息。
使用 runtime 包獲取 goroutine id
雖然不能直接獲取每個 goroutine 的 id,但我們可以變相的通過 runtime.Stack 函數(shù)來獲取。
實現(xiàn)代碼如下:
github.com/jianghushinian/blog-go-example/blob/main/goroutine/id/main.go
package main
import (
"fmt"
"runtime"
"strconv"
"strings"
"sync"
)
func GoId() int {
buf := make([]byte, 32)
n := runtime.Stack(buf, false)
idField := strings.Fields(strings.TrimPrefix(string(buf[:n]), "goroutine "))[0]
id, err := strconv.Atoi(idField)
if err != nil {
panic(fmt.Sprintf("cannot get goroutine id: %v", err))
}
return id
}
func main() {
fmt.Println("main", GoId())
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
i := i
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println(i, GoId())
}()
}
wg.Wait()
}
這段代碼通過自定義的 GoId 函數(shù)來獲取當前 goroutine 的 id,并在 main 函數(shù)的主 goroutine 和子 goroutines 中打印它們的 id。
我們來解釋下 GoId 函數(shù)主要邏輯的實現(xiàn):
1.runtime.Stack(buf, false):
runtime.Stack是runtime包提供的公開函數(shù),用于獲取當前 goroutine 的堆棧信息。- 參數(shù)
buf是一個字節(jié)數(shù)組,用來存儲調用runtime.Stack()返回的堆棧信息。 - 第二個參數(shù)是
false,表示我們只獲取當前 goroutine 的棧信息,如果為true則是獲取所有 goroutines 的棧信息。 runtime.Stack()會將當前 goroutine 的棧信息寫入buf中,n是返回的字節(jié)數(shù),表示堆棧信息的長度。
2.strings.TrimPrefix(string(buf[:n]), "goroutine "):
goroutine 1 [running]:
- 將堆棧信息轉為字符串并去掉前綴
"goroutine "。 - 堆棧信息的第一行格式通常如下:
- 這里通過
TrimPrefix去除前綴"goroutine "后,剩下的內容就是 goroutine 的 id 及其狀態(tài)信息。
3.idField := strings.Fields(...)[0]:
strings.Fields將經(jīng)過TrimPrefix處理后的字符串按空格切割成一個字符串切片。- 從切片中獲取第一個字段,這就是 goroutine 的 id,如
1。
如果一切順利,GoId 函數(shù)最終返回當前 goroutine 的 id。
main 函數(shù)實現(xiàn)則比較簡單,先調用 GoId() 打印主 goroutine 的 id,然后啟動了 10 個子 goroutine 并分別打印它們的 id。
執(zhí)行示例代碼,得到如下輸出:
$ go run main.go
main 1
9 29
0 20
5 25
6 26
7 27
8 28
2 22
1 21
4 24
3 23
這樣,我們就變相的通過先獲取堆棧信息,然后再從堆棧信息中進行解析的方式拿到了 goroutine 的 id。想必你也能夠發(fā)現(xiàn),這種實現(xiàn)方式性能不高,所以不到萬不得已,不要輕易獲取 goroutine 的 id。
那么有沒有更高效的方式呢?
很遺憾,Go 官方?jīng)]有提供。不過有第三方庫幫我們實現(xiàn)了。
使用第三方庫獲取 goroutine id
一個比較常用的庫是 github.com/petermattis/goid,可以用來獲取當前 goroutine 的 id。
安裝方式:
$ go get github.com/petermattis/goid
使用示例:
github.com/jianghushinian/blog-go-example/blob/main/goroutine/id/goid/main.go
package main
import (
"fmt"
"sync"
"github.com/petermattis/goid"
)
func main() {
fmt.Println("main", goid.Get())
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
i := i
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println(i, goid.Get())
}()
}
wg.Wait()
}
執(zhí)行示例代碼,得到如下輸出:
$ go run goid/main.go
main 1
9 43
4 38
5 39
6 40
7 41
8 42
1 35
0 34
2 36
3 37
我們僅需要調用 goid.Get() 即可獲取當前 goroutine 的 id。
goid 庫使用了 C 和 匯編來獲取 goroutine id,所以性能更好。并且 goid 為所有的 Go 版本都做了兼容,從項目文件名可以看出,不同 Go 版本有著不同的實現(xiàn):
$ tree goid goid ├── LICENSE ├── README.md ├── go.mod ├── goid.go ├── goid_gccgo.go ├── goid_go1.3.c ├── goid_go1.3.go ├── goid_go1.4.go ├── goid_go1.4.s ├── goid_go1.5.go ├── goid_go1.5.s ├── goid_slow.go ├── goid_test.go ├── runtime_gccgo_go1.8.go ├── runtime_go1.23.go ├── runtime_go1.5.go ├── runtime_go1.6.go └── runtime_go1.9.go 1 directory, 18 files
在 goid_go1.3.c 中可以看到 C 語言版本實現(xiàn)如下:
github.com/petermattis/goid/blob/master/goid_go1.3.c
// +build !go1.4
#include <runtime.h>
void ·Get(int64 ret) {
ret = g->goid;
USED(&ret);
}
在 goid_go1.4.s 中可以看到匯編語言版本實現(xiàn)如下:
github.com/petermattis/goid/blob/master/goid_go1.4.s
// +build amd64 amd64p32 arm 386 // +build go1.4,!go1.5 #include "textflag.h" #ifdef GOARCH_arm #define JMP B #endif TEXT ·getg(SB),NOSPLIT,$0-0 JMP runtime·getg(SB)
此外,為了保證兼容性,在 goid.go 中還有一個 Go 語言版本實現(xiàn):
github.com/petermattis/goid/blob/master/goid.go
package goid
import (
"bytes"
"runtime"
"strconv"
)
func ExtractGID(s []byte) int64 {
s = s[len("goroutine "):]
s = s[:bytes.IndexByte(s, ' ')]
gid, _ := strconv.ParseInt(string(s), 10, 64)
return gid
}
// Parse the goid from runtime.Stack() output. Slow, but it works.
func getSlow() int64 {
var buf [64]byte
return ExtractGID(buf[:runtime.Stack(buf[:], false)])
}
這里 Go 版本的實現(xiàn)同樣使用 runtime.Stack(),并且注釋也標明了這個實現(xiàn)比較慢。
所以,如果我們真的需要獲取 goroutine 的 id,那么推薦使用 goid。
總結
在 Go 中獲取當前進程的 id 可以使用 os.Getpid() 函數(shù)。如果要獲取當前 goroutine 的 id 則要困難一些,Go 標準庫沒有直接提供該功能,不過我們可以變相的從 runtime.Stack() 返回的堆棧信息中獲取,也可以使用第三方庫 goid 來獲取。
以上就是Go語言如何獲取goroutine的id的詳細內容,更多關于Go獲取goroutine的id的資料請關注腳本之家其它相關文章!
相關文章
Go語言編程中判斷文件是否存在是創(chuàng)建目錄的方法
這篇文章主要介紹了Go語言編程中判斷文件是否存在是創(chuàng)建目錄的方法,示例都是使用os包下的函數(shù),需要的朋友可以參考下2015-10-10

