Golang通过修改源码编译的方式获取goroutine id

Golang source compile and get goroutine id

Posted by alovn on October 24, 2021

这篇文章主要介绍如何编译Go的源码,学习后可以自己为Go添加一些定制化的功能,或许也可以为Go贡献自己的代码(仅仅会编译是不够的,还必须深入理解Go的运行机制和源码实现)。这里将通过修改Go的源码添加一个获取Goroutine编号的函数为例,介绍如何编译。

一般编程语言中都有线程,通过给线程分配编号,我们可以记录与某线程有关的数据,比如Java中的ThreadLocal。Golang没有提供线程,而是使用了更轻量化的Goroutine,同时Go也给每个Goroutine分配了一个编号,但是并没有提供获取Goroutine编号的函数。

这里注意目的是介绍如何修改和编译Go的源码,而不是为了给Go添加暴露Goroutine编号的函数,因为一般Go开发的应用中会存在大量的goroutine,很多的Goroutine运行时间较为短暂,所以获取到goroutine id后的意义并不大。

准备工作

  • Go: Go编译的工具链是用Go编写的。但是早期Go语言的编译器用C语言写的,Go1.4是最后一个C语言的版本,之后的版本是使用Go编写的工具编译Go。所以编译Golang源码之前需要安装好一个版本的Go,可以使用基于C语言的Go1.4或使用之后基于Golang的其它版本。之外还需要设置环境变量GOROOT_BOOTSTRAP指向go所在的目录。
  • C语言编译器: 需要安装gcc或clang。由于Go支持cgo,需要导入C语言的库,所以需要安装C语言的编译器。windows下可以安装mingw,注意32位还是64位。若不需要cgo,可以设置环境变量 CGO_ENABLED=0。
  • Git: 需要安装Git。
  • Go的源码。

开始编译

做完以上准备后,可以开始编译:

1
2
3
4
5
git clone [email protected]:golang/go.git # 下载源码
cd go
git checkout -b go1.17.2 go1.17.2 # 某分支或tag的版本
cd src
./all.bat # 执行编译,Linux、MacOS上为all.bash,Windows下为 all.bat

如果一切正常,会看到以下输出:

1
2
3
4
5
6
ALL TESTS PASSED

---
Installed Go for windows/amd64 in D:\workspace\go
Installed commands in D:\workspace\go\bin
*** You need to add D:\workspace\go\bin to your PATH.

编译好的二进制文件会生成在bin目录下。

可以写一段简单的代码测试一下编译好的go能否正常运行。创建一个hello.go文件:

1
2
3
4
5
6
7
package main

import "fmt"

func main() {
    fmt.Printf("hello, world\n")
}

直接使用go来运行:

1
2
$ go run hello.go
hello, world

增加goroutine id

其实每个goroutine都有一个id,只是Go没有将它暴露出来。Go开发的应用中一般存在大量的goroutine,大多数goroutine生命周期比较短暂,所以暴露出goroutine id意义并不大,它并不能给追踪问题带来较大帮助,这里只是演示和测试。

在源码src/runtime/proc.go中添加以下代码:

1
2
3
func GetGoroutineID() int64 {
    return getg().goid
}

上面getg()函数为获得当前执行的g对象,它包含了当前goroutine相关的信息。然后重新执行编译:

1
.\all.bat

编译成功后,写代码测试一下。新建一个goid.go:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import (
    "fmt"
    "runtime"
    "sync"
)

func PrintGoroutineID(wg *sync.WaitGroup) {
    fmt.Println("hello, goroutine id:", runtime.GetGoroutineID())
    wg.Done()
}
func main() {
    wg := sync.WaitGroup{}
    wg.Add(2)
    go PrintGoroutineID(&wg)
    go PrintGoroutineID(&wg)
    wg.Wait()
}

然后执行:

1
2
3
$ go run ./goid.go
hello, goroutine id: 7
hello, goroutine id: 6

可以看到已经可以获取到并输出goroutine id了。