• 了解 runtime 包中的的环境变量
  • 了解 runtime 包中的基本函数及使用

目录

1. 概述

pkg runtime

Packages runtime conatins operations that interact(相互影响、相互作用) with Go’s runtime system, such as functions to control goroutines. It also includes the low-level type information used by the reflect package.

包 runtime 包含了和 Go 的运行时系统进行交互的一些操作,例如控制 go 程的功能。它也包含了被反射包使用的低级别类型信息。

Q: 何为 low-level type?

2. 环境变量

The following environment variables ($name or %name%, depending on the host operating system) control the run-time behavior of Go programs. The meanings and use may change from release to release.

以下环境变量决定了Go程序的具体运行时的表现。

2.1. GOGC

The GOGC variable sets the initial garbage collection target percentage. A collection is triggered(触发) when the ratio(比率) of freshly(新鲜的、新近的) allocated(分配) data to live(v. 活着) data remaining after the previous collection reaches this percentage. The default is GOGC=100. Setting GOGC=off disables(禁用) the garbage collector entirely. The runtime/debug package’s SetGCPercent function allows changing this percentage at run time. See https://golang.org/pkg/runtime/debug/#SetGCPercent.

GOGC 变量用于设置初始的垃圾收集目标的百分比。当新分配的数据与先前收集后剩余数据的实时比率达到此百分比时,会触发收集。

2.2. GODEBUG

The GODEBUG variable controls debugging variables within the runtime. It is a comma-separated(逗号分隔) list of name=val pairs setting these named variables:

GODEBUG 变量用于控制runtime内的调试变量。它是一个以逗号分隔的 name=val 对列表,用于设置这些命名变量。

1
// some name=val pairs ...

2.3. GOMAXPROCS

The GOMAXPROCS variable limits the number of operating system threads that can execute user-level Go code simultaneously(同时). There is no limit to the number of threads that can be blocked in system calls on behalf(代表) of Go code; those do not count against the GOMAXPROCS limit. This package’s GOMAXPROCS function queries and changes the limit.

GOMAXPROCS 变量用于限制可以同时执行用户级Go代码的操作系统线程的数量

2.4. GOTRACEBACK

The GOTRACEBACK variable controls the amount of output generated when a Go program fails due to an unrecovered panic or an unexpected runtime condition.

GOTRACEBACK 变量控制错误导致的输出数量

2.5. GOARCH、GOOS、GOPATH、GOROOT

1
2
3
4
GOARCH: 系统架构386   | amd64
GOOS:   系统类型linux | windows
GOPATH: 开发目录
GOROOT: 安装目录

3. 基本函数

1
2
// NumCPU() 返回本地主机得逻辑CPU数量
func NumCPU() int
1
2
// GOROOT returns the root of the Go tree
func GOROOT() string
1
2
// GOOS() 查看目标操作系统; 很多时候,我们会根据平台的不同实现不同的操作,就需要用GOOS了
func GOOS() string
1
2
// 设置可以同时进行运算的 CPU 数量;如果 n<1, 则不改变当前的值; 可以通过上面的 NumCPU 获的参数 n 
func GOMAXPROCS(n int) n
1
2
// GC 运行垃圾回收
func GC()
1
2
// 终止调用它的 goroutine, 其他 goroutine 不受影响
func Goexit()
1
2
3
4
// Gosched 函数的作用是让当前的 goroutine 让出 CPU,当一个 goroutine 发生阻塞,
// Go 会自动的把与该 goroutine 处于同一系统线程线程下的其他 goroutine 转移到另
// 外一个系统线程上去执行,使得这些 goroutine 不会被阻塞
func Gosched()
1
2
// NumGoroutine 返回当前存在的 go 程数量
func NumGoroutine() int
1
2
// type Func
type Func struct { /* contains filtered or unexported fields */ }
1
2
// Entry 返回函数的入口地址
func (f *Func) Entry() uintptr 
1
2
// FileLine 根据函数的入口地址,返回文件名及源代码行数
func (f *Func) FileLine(pc uintptr) (file string, line int)
1
2
// Name 返回函数名
func (f *Func) Name() string

4. 函数使用

4.1. Go 语言的函数调用信息

函数的调用信息是程序中比较重要运行期信息, 在很多场合都会用到(比如调试或日志)

  • runtime 包的 runtime.Caller、runtime.Callers、runtime.FuncForPc 等几个函数提供了获取函数掉用者信息的方法. (Note: 获取函数调用信息都是从子节点到父节点追踪的一个过程)

4.1.1. runtime.Caller 用法

函数签名及释义:

1
2
3
4
5
6
// Caller 返回当前 goroutine 的栈上的函数调用信息, 包括当前的pc值,调用的文件及行号。若无法获得信息,返回的 ok 将会是 false.
// 
// skip 表示要跳过的栈帧数,skip=0 表示 runtime.Caller 的调用者
// 
// Note: 由于历史原因, runtime.Caller 和 runtime.Callers 中的 skip 含义并不相同
func Caller(skip int) (pc uintptr, file string, line int, ok bool)

关于 runtime.Caller 的 demo: 输出函数的栈帧调用信息

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
func main() {
    funcCallerInfo()
}

func funcCallerInfo() {
	for skip := 0; ; skip++ {
		pc, file, line, ok := runtime.Caller(skip)
		if !ok {
			break
		}
		log.Printf("skip: %v, pc: %v, file: %v, line: %v\n", skip, pc, file, line)
	}

	// output:
	// 2018/06/14 10:11:24 skip: 0, pc: 4812470, file: D:/code/Go_Path/src/instance.golang.com/base-lib/runtime.go, line: 77
	// 2018/06/14 10:11:24 skip: 1, pc: 4812054, file: D:/code/Go_Path/src/instance.golang.com/base-lib/runtime.go, line: 16
	// 2018/06/14 10:11:24 skip: 2, pc: 4366477, file: D:/Go/src/runtime/proc.go, line: 198
	// 2018/06/14 10:11:24 skip: 3, pc: 4516640, file: D:/Go/src/runtime/asm_amd64.s, line: 2361
}

Note:

  • skip=0 时,runtime.Caller 返回的调用信息为:当前文件(runtime.go)里的 main.funcCallerInfo 函数,以及所在的行号
  • skip=1 时,runtime.Caller 返回的调用信息为:跳过 skip=0 时的调用,向上一级查找父节点的调用堆栈信息,即:当前文件(runtime.go)里的 main.main 函数,以及调用funcCallerInfo 函数所在的行号
  • skip=2 时,调用函数是:proc.go 里的 runtime.main 函数
  • skip=3 时,调用函数是:asm_amd64.s 里的 runtime.goexit (TEXT runtime·goexit(SB),NOSPLIT,$0-0)

由此可知:Go 的普通程序启动顺序如下:

  • runtime.goexit 为真正的函数入口(并不是 runtime.main)
  • 然后 runtime.goexit 调用 runtime.main 函数
  • 最后 runtime.main 函数调用用户编写 main.main 函数

4.1.2. runtime.Callers 用法

函数签名及释义:

1
2
3
4
5
6
7
// Callers 把调用它的函数在 goroutine 的栈上的程序计数器填入切片 pc 中.
// 
// skip 表示在开始往 pc 切片中记录栈帧数之前所要跳过的栈帧数. 若为0,则表示从
// runtime.Callers 自身的栈帧开始记录。若为1,则表示从调用者的栈帧开始记录. 
// 
// 该函数返回写入到 pc 切片中的项数(受切片的容量限制) 
func Callers(skip int, pc []uintptr) int 

关于 runtime.Callers 的 demo: 用于输出每个栈帧的 pc 信息

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// funcCallersDemo 输出每个栈帧的 pc(program counters) 信息
func funcCallersDemo() {
	pc := make([]uintptr, 1024)
	for skip := 0; ; skip++ {
		i := runtime.Callers(skip, pc)
		if i <= 0 {
			break
		}
		log.Printf("skip: %v, pc: %v\n", skip, pc[:i])
	}

	// output:
	// 2018/06/14 14:33:30 skip: 0, pc: [4219048 4812589 4812199 4366622 4516785]
	// 2018/06/14 14:33:30 skip: 1, pc: [4812589 4812199 4366622 4516785]
	// 2018/06/14 14:33:30 skip: 2, pc: [4812199 4366622 4516785]
	// 2018/06/14 14:33:30 skip: 3, pc: [4366622 4516785]
	// 2018/06/14 14:33:30 skip: 4, pc: [4516785]
}

Note:

  • 输出新的 pc 长度和 skip 大小有逆相关性. skip = 0 为 runtime.Callers 自身的信息.
  • 这个例子比前一个例子多输出了一个栈帧, 就是因为多了一个runtime.Callers栈帧的信息(前一个例子是没有runtime.Caller信息的(注意:没有s后缀)).

4.1.3. runtime.FuncForPC 用法

函数签名如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// runtime.FuncForPC 返回给定地址 pc 的函数信息,如果 pc 无效,则返回 nil;
func runtime.FuncForPC(pc uintptr) *runtime.Func 

// runtime.Func.Name 返回该函数的名称
func (f *runtime.Func) Name() string

// runtime.Func.Entry 对应函数的地址
func (f *runtime.Func) Entry() uintptr

// runtime.Func.FileLine 返回与 pc 对应的源码文件名和行号. 按照文档的说明, 如果pc不在函数帧范围内, 则结果是不确定的.
func (f *runtime.Func) FileLine(pc uintptr) (file string, line int)

关于 runtime.FuncForPC 的 demo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
func funcForPCDemo() {
	for skip := 0; ; skip++ {
		pc, _, _, ok := runtime.Caller(skip)
		if !ok {
			// 无法获得信息
			break
		}
		f := runtime.FuncForPC(pc)
		file, line := f.FileLine(0)
		log.Printf("skip: %v, pc: %v\n", skip, pc)
		log.Printf("\t file: %v, line: %v\n", file, line)
		log.Printf("\t entry: %v\n", f.Entry())
		log.Printf("\t name: %v\n", f.Name())
	}
	println()

	// output:
	// 2018/06/14 15:42:25 skip: 0, pc: 4813275
	// 2018/06/14 15:42:25      file: D:/code/Go_Path/src/instance.golang.com/base-lib/runtime.go, line: 113
	// 2018/06/14 15:42:25      entry: 4812368
	// 2018/06/14 15:42:25      name: main.funcForPCDemo
	// 2018/06/14 15:42:25 skip: 1, pc: 4812342
	// 2018/06/14 15:42:25      file: D:/code/Go_Path/src/instance.golang.com/base-lib/runtime.go, line: 15
	// 2018/06/14 15:42:25      entry: 4812304
	// 2018/06/14 15:42:25      name: main.main
	// 2018/06/14 15:42:25 skip: 2, pc: 4366621
	// 2018/06/14 15:42:25      file: D:/Go/src/runtime/proc.go, line: 109
	// 2018/06/14 15:42:25      entry: 4366096
	// 2018/06/14 15:42:25      name: runtime.main
	// 2018/06/14 15:42:25 skip: 3, pc: 4516928
	// 2018/06/14 15:42:25      file: D:/Go/src/runtime/asm_amd64.s, line: 2361
	// 2018/06/14 15:42:25      entry: 4516928
	// 2018/06/14 15:42:25      name: runtime.goexit

	pc := make([]uintptr, 1024)
	for skip := 0; ; skip++ {
		i := runtime.Callers(skip, pc)
		if i <= 0 {
			break
		}
		log.Printf("skip: %v, pc: %v\n", skip, pc[:i])

		for j := 0; j < i; j++ {
			f := runtime.FuncForPC(pc[j])
			file, line := f.FileLine(0)
			log.Printf("skip: %v, pc: %v\n", skip, pc[j])
			log.Printf("\t file: %v, line: %v\n", file, line)
			log.Printf("\t entry: %v\n", f.Entry())
			log.Printf("\t name: %v\n", f.Name())
		}
		break
	}

	// output:
	// 2018/06/14 15:42:25 skip: 0, pc: [4219048 4813404 4812343 4366622 4516929]
	// 2018/06/14 15:42:25 skip: 0, pc: 4219048
	// 2018/06/14 15:42:25      file: D:/Go/src/runtime/extern.go, line: 205
	// 2018/06/14 15:42:25      entry: 4218944
	// 2018/06/14 15:42:25      name: runtime.Callers
	// 2018/06/14 15:42:25 skip: 0, pc: 4813404
	// 2018/06/14 15:42:25      file: D:/code/Go_Path/src/instance.golang.com/base-lib/runtime.go, line: 113
	// 2018/06/14 15:42:25      entry: 4812368
	// 2018/06/14 15:42:25      name: main.funcForPCDemo
	// 2018/06/14 15:42:25 skip: 0, pc: 4812343
	// 2018/06/14 15:42:25      file: D:/code/Go_Path/src/instance.golang.com/base-lib/runtime.go, line: 15
	// 2018/06/14 15:42:25      entry: 4812304
	// 2018/06/14 15:42:25      name: main.main
	// 2018/06/14 15:42:25 skip: 0, pc: 4366622
	// 2018/06/14 15:42:25      file: D:/Go/src/runtime/proc.go, line: 109
	// 2018/06/14 15:42:25      entry: 4366096
	// 2018/06/14 15:42:25      name: runtime.main
	// 2018/06/14 15:42:25 skip: 0, pc: 4516929
	// 2018/06/14 15:42:25      file: D:/Go/src/runtime/asm_amd64.s, line: 2361
	// 2018/06/14 15:42:25      entry: 4516928
	// 2018/06/14 15:42:25      name: runtime.goexit
}

Note: 根据测试, 如果是无效 pc (比如0), runtime.Func.FileLine 一般会输出当前函数的开始行号. 不过在实践中, 一般会用 runtime.Caller 获取文件名和行号信息, runtime.Func.FileLine 很少用到(如何独立获取pc参数?).

5. See Also

Thanks to the authors 🙂

返回目录