Goroutine是Golang中轻量级线程的实现,由Go Runtime管理。Golang在语言级别支持轻量级线程,叫做协程。Golang标准库提供的所有系统调用操作,都会出让CPU给其他Goroutine。这让事情变得非常简单,让轻量级线程的切换管理不依赖于系统的线程和进程,也不依赖于CPU的核心数量。
Goroutine非常亮眼,但是自从go1.4版本以后,GoroutineId无法直接从Go Runtime获取了。这是Golang的开发者故意为之,避免开发者滥用Goroutine Id实现Goroutine Local Storage,因为Goroutine Local Storage很难进行垃圾回收。
通过汇编获取
复杂度高,便宜地址随版本可能有变化,不建议使用
通过第三方库获取
相关第三方库可以在github上找,比如:
https://github.com/jtolds/gls
https://github.com/huandu/goroutine
稳定性未知,性能也不高,不建议使用
通过runtime.Stack获取
它利用runtime.Stack的堆栈信息,将当前的堆栈信息写入到一个slice中,堆栈的第一行为”goroutine ###[…”,其中”###”就是当前的Goroutine Id,通过这个方法就可以实现Goid函数了。
Goid函数的实现如下:
func Goid() int {
defer func() {
if err := recover(); err != nil {
fmt.Println("panic recover:panic info:%v", err) }
}()
var buf [64]byte
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
}
通过修改编译器源码获取
在go源码runtime包中增加函数Goid,直接调用runtine的getg函数获取,具有简单高校稳定的优点,同时每个团队可以通过容器来部署自己的微服务。
最佳实践
下载go1.4版本的编译器
在Golang的官方网站下载go1.4版本的编译器,URL如下:
https://golang.org/dl/
解压缩,将go文件夹rename成go1.4,然后移动到$HOME目录下。
修改go1.7.3版本的编译器代码
在Golang的官方网站下载go1.7.3版本的源码。编辑src/runtime/proc.go文件,在尾部添加函数Goid:
func Goid() int64 {
_g_ := getg()
return _g_.goid
}
运行src/make.bash命令(默认使用$HOME/go1.4目录下编译器),编译go1.7.3的新版本。编译完成后,将go文件夹拷贝到GOROOT目录下,使之生效:
$ go version
go version go1.7.3 linux/amd64
测试代码
我们模拟一个完全可以并行的计算任务:计算N个整型数的总和。我们可以将所有整型数分成M份,M即CPU的个数。让每个CPU开始计算分给它的那份计算任务,最后将每个CPU的计算结果再做一次累加,这样就可以得到所有N个整型数的总和,实现代码如下:
type Vector []int
func (v Vector) DoSome(i, n int, u Vector, c chan int, add *int) int {
for ; i < n; i++ {
*add += u[i]
}
id := runtime.Goid(id)
fmt.Println("id:", id)
c <- 1
return 1
}
const NCPU = 16
func (v Vector) DoAll(u Vector) int {
c := make(chan int, NCPU)
var add [NCPU]int
sum := 0
for i := 0; i < NCPU; i++ {
go v.DoSome(i * len(v) / NCPU, (i + 1)* len(v) / NCPU, u, c, &add[i])
}
for i := 0; i < NCPU; i++ {
<- c
}
for i := 0; i < NCPU; i++ {
sum += add[i]
}
return sum
}
func main() {
x := 0
y := 0
v := make(Vector, 160)
for i := 0; i < 160; i++ {
v[i] = i
x += i
}
y = v.DoAll(v)
fmt.Println("x =", x, "and y =", y)
}
日志
通过查看日志,我们已将成功获取到了Goroutine Id。
id: 20
id: 13
id: 7
id: 12
id: 14
id: 9
id: 5
id: 17
id: 16
id: 10
id: 6
id: 15
id: 18
id: 19
id: 8
id: 11
x = 12720 and y = 12720
适配层封装
我们可以将glog等第三方库的日志接口进行简单封装,隐藏goid的获取和打印过程,使得用户轻松。