如果你使用过如 python、java 等主流支持并发的编程语言,那么通常都能够比较容易的获得进程和线程的 id。但是在 go 语言,没有直接提供对多进程和多线程的支持,而是提供了 goroutine 来支持并发编程。不过在 go 中,获取 goroutine 的 id 并不像其他编程语言那样容易,但依然有办法,本文就来介绍下如何实现。
获取当前进程的 id
首先,虽然 go 没有提供多进程编程,但启动 go 程序还是会有一个进程存在的,go 标准库提供了 os.getpid 函数,可以方便的获取当前进程的 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 函数来获取。
实现代码如下:
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 函数来获取当前 goroutine 的 id,并在 main 函数的主 goroutine 和子 goroutines 中打印它们的 id。
我们来解释下 goid 函数主要逻辑的实现:
1.runtime.stack(buf, false):
runtime.stack是runtime包提供的公开函数,用于获取当前 goroutine 的堆栈信息。- 参数
buf是一个字节数组,用来存储调用runtime.stack()返回的堆栈信息。 - 第二个参数是
false,表示我们只获取当前 goroutine 的栈信息,如果为true则是获取所有 goroutines 的栈信息。 runtime.stack()会将当前 goroutine 的栈信息写入buf中,n是返回的字节数,表示堆栈信息的长度。
2.strings.trimprefix(string(buf[:n]), "goroutine "):
goroutine 1 [running]:
- 将堆栈信息转为字符串并去掉前缀
"goroutine "。 - 堆栈信息的第一行格式通常如下:
- 这里通过
trimprefix去除前缀"goroutine "后,剩下的内容就是 goroutine 的 id 及其状态信息。
3.idfield := strings.fields(...)[0]:
strings.fields将经过trimprefix处理后的字符串按空格切割成一个字符串切片。- 从切片中获取第一个字段,这就是 goroutine 的 id,如
1。
如果一切顺利,goid 函数最终返回当前 goroutine 的 id。
main 函数实现则比较简单,先调用 goid() 打印主 goroutine 的 id,然后启动了 10 个子 goroutine 并分别打印它们的 id。
执行示例代码,得到如下输出:
$ 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。想必你也能够发现,这种实现方式性能不高,所以不到万不得已,不要轻易获取 goroutine 的 id。
那么有没有更高效的方式呢?
很遗憾,go 官方没有提供。不过有第三方库帮我们实现了。
使用第三方库获取 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()
}
执行示例代码,得到如下输出:
$ 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 版本有着不同的实现:
$ 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 语言版本实现如下:
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 中可以看到汇编语言版本实现如下:
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 语言版本实现:
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 版本的实现同样使用 runtime.stack(),并且注释也标明了这个实现比较慢。
所以,如果我们真的需要获取 goroutine 的 id,那么推荐使用 goid。
总结
在 go 中获取当前进程的 id 可以使用 os.getpid() 函数。如果要获取当前 goroutine 的 id 则要困难一些,go 标准库没有直接提供该功能,不过我们可以变相的从 runtime.stack() 返回的堆栈信息中获取,也可以使用第三方库 goid 来获取。
以上就是go语言如何获取goroutine的id的详细内容,更多关于go获取goroutine的id的资料请关注代码网其它相关文章!
发表评论