Golang生成的可执行文件在指定平台即可运行效率很高,它和C/C++一样编译出来的是二进制可执行文件,运行Go程序并不需要安装类似Java虚拟机之类的东西,是因为编译时Go会将runtime
部分代码编译进去。
-
runtime
类似Java中的JVM虚拟机,不过并非是虚拟机。 -
runtime
核心功能包括并发调度、垃圾回收、内存管理...
![](https://img.haomeiwen.com/i4933701/add05d90265cb6c9.png)
系统信息
字段 | 描述 |
---|---|
runtime.Compiler | 编译工具链,用于构建可执行的二进制文件。例如gc/gccgo |
runtime.GOARCH | 可执行程序的目标处理器架构,例如386/amd64/arm |
runtime.GOOS | 可执行程序的目标操作系统,例如windows/linux/darwin |
runtime.Version() | Go版本字符串 |
runtime.NumCPU() | 本机逻辑CPU个数 |
runtime.NumGoroutine() | 当前存在的Go程数 |
例如:获取操作系统信息
type OS struct {
//编译工具链,用于构建可执行的二进制文件。例如gc/gccgo
Compiler string `json:"compiler"`
//可执行程序的目标处理器架构,例如386/amd64/arm
GOARCH string `json:"goarch"`
//可执行程序的目标操作系统,例如windows/linux/darwin
GOOS string `json:"goos"`
//Go版本字符串
Version string `json:"version"`
//本机逻辑CPU个数
NumCPU int `json:"numcpu"`
//当前存在的Go程数
NumGoroutine int `json:"numgoroutine"`
}
func InitOS() (o OS) {
o.Compiler = runtime.Compiler
o.GOARCH = runtime.GOARCH
o.GOOS = runtime.GOOS
o.Version = runtime.Version()
o.NumCPU = runtime.NumCPU()
o.NumGoroutine = runtime.NumGoroutine()
return
}
fmt.Printf("%+v\n", InitOS())
{Compiler:gc GOARCH:amd64 GOOS:windows Version:go1.17.5 NumCPU:6 NumGoroutine:1}
runtime.Caller
runtime.Caller
用来获取当前goroutine
函数调用栈的程序计数器及其相关信息
在日志打印时可以使用runtime.Caller
,用来获取当前调用栈所在的文件行号和文件信息,以方便开发人员在同一位置获取需要的调用处的文件信息。
函数签名
func Caller(skip int) (pc uintptr, file string, line int, ok bool)
入参 | 描述 |
---|---|
skip | 需要提升的堆栈帧数 |
skip = 0 | 表示当前函数 |
skip = 1 | 表示上一层函数 |
返回值 | 描述 |
---|---|
pc | 函数指针 |
file | 函数所在文件的名称或目录名称 |
line | 函数所在文件的行号 |
ok | 是否可以获取到信息 |
例如:获取当前函数堆栈信息,可用于获取当前执行文件的文件路径。
package main
import (
"fmt"
"runtime"
)
func main() {
pc, file, line, ok := runtime.Caller(0)
fmt.Printf("pc = %v, file = %v, line = %v, ok = %v\n", pc, file, line, ok)
}
pc = 1863716, file = F:/Go/project/gfw/test/main.go, line = 9, ok = true
runtime.Callers
runtime.Callers
用来返回调用栈的程序计数器,需存放到一个uintptr
类型中。
func Callers(skip int, pc []uintptr) int
runtime.Callers
会将调用它的函数goroutine
上的程序计数器填入切片pc
中,参数skip
表示开始在pc
中记录之前所要跳过的栈桢数。当skip
为0时表示runtime.Callers
自身的栈帧,若为1则表示调用者的栈帧。
runtime.Callers
函数返回写入到pc
切片中的项数,项数收切片容量的限制。
例如:使用runtime.Callers
输出每个栈帧的PC信息
package main
import (
"fmt"
"runtime"
)
func test() {
trace()
}
func main() {
test()
}
func trace() {
pcs := make([]uintptr, 10)
n := runtime.Callers(0, pcs)
for i := 0; i < n; i++ {
pc := pcs[i]
fn := runtime.FuncForPC(pc)
entry := fn.Entry()
fname := fn.Name()
file, line := fn.FileLine(pc)
fmt.Printf("%d %v %s:%d %s\n", i, entry, file, line, fname)
}
}
$ go run main.go
0 15495392 F:/Go/go/src/runtime/extern.go:216 runtime.Callers
1 15495392 F:/Go/go/src/runtime/extern.go:216 runtime.Callers
2 15495328 F:/Go/project/gfw/test/main.go:12 main.main
3 15495328 F:/Go/project/gfw/test/main.go:12 main.main
4 15040320 F:/Go/go/src/runtime/proc.go:213 runtime.main
5 15221568 F:/Go/go/src/runtime/asm_amd64.s:1375 runtime.goexit
定制CallerName函数返回调用者的函数名/文件名/行号等用户友好的信息
package main
import (
"fmt"
"runtime"
)
func main() {
trace()
}
func trace() {
for skip := 0; ; skip++ {
name, file, line, ok := CallerName(skip + 1)
if !ok {
break
}
fmt.Printf("%v %s:%d %v\n", skip, file, line, name)
}
}
func CallerName(skip int) (name, file string, line int, ok bool) {
var pc uintptr
//执行runtime.Caller调用时,skip+1用于抵消CallerName函数自身的调用。
if pc, file, line, ok = runtime.Caller(skip + 1); !ok {
return
}
name = runtime.FuncForPC(pc).Name()
return
}
$ go run main.go
0 F:/Go/project/gfw/test/main.go:9 main.main
1 F:/Go/go/src/runtime/proc.go:204 runtime.main
2 F:/Go/go/src/runtime/asm_amd64.s:1374 runtime.goexit
runtime.CallersFrames
使用runtime.Callers
只能获取栈的程序计数器,若想要获取整个栈信息可使用runtime.CallersFrames
函数,省去遍历调用FuncForPC
环节。
func runtime.CallersFrames(callers []uintptr) *runtime.Frames
运行时的用户应避免直接检查所产生的PC切片,而应该使用runtime.CallersFrames
来获取调用堆栈的完整视图。
运行时runtime.Caller
获取有关单个调用者的信息,PC切片的单个元素不能解释调用堆栈的内联框架或其他细微差别。
具体来说,直接迭代PC切片并使用诸如runtime.FuncForPC
之类功能代码单独解析每个PC将会错误内联框架。要获得堆栈的完整视图,这些代码应改为使用runtime.CallersFrames
。同样,代码不应该假设runtime.Callers
返回的长度是呼叫深度的任何指示。它应该计算runtime.CallersFrames
返回帧数。在特定深度查询单个呼叫者的代码时应该使用Caller
,而不是将长度为1的片段传递给Callers
。
func trace() {
pcs := make([]uintptr, 10)
n := runtime.Callers(0, pcs)
pc := pcs[:n]
frames := runtime.CallersFrames(pc)
for {
frame, more := frames.Next()
fmt.Printf("%s:%d %s\n", frame.File, frame.Line, frame.Function)
if !more {
break
}
}
}
uintptr
uintptr
是Golang内置类型,能存储指针的类型,在64位平台上底层的数据类型是
typedef unsigned long long int uint64;
typedef uint64 uintptr;
uintptr
是一个整数类型用于指针运算,垃圾回收器GC
不会将uintptr
类型作为指针,也就是说uintptr
无法持有对象,uintptr
类型的目标会被回收。
uintptr
变量表示的地址处的数据也可能被GC回收
runtime.FuncForPC
runtime.FuncForPC
用于获取一个标识调用栈标识符pc
对应的调用栈
函数签名
// 获取包含给定PC地址的函数,无效则返回nil。
func runtime.FuncForPC(pc uintptr) *runtime.Func
// 获取函数名称
func (f *runtime.Func) Name() string
// 获取与PC对应的源码文件名和行号,若PC不在函数帧范围内则结果不确定。
func (f *runtime.Func) FileLine(pc uintptr) (file string, line int)
// 获取对应函数的地址
func (f *runtime.Func) Entry() uintptr
例如:
package main
import (
"fmt"
"runtime"
)
func main() {
pc, file, line, ok := runtime.Caller(0)
fmt.Printf("pc = %v, file = %v, line = %v, ok = %v\n", pc, file, line, ok)
fn := runtime.FuncForPC(pc)
//获取调用栈所调用的函数的名字
fname := fn.Name()
fmt.Printf("fname = %v\n", fname) //fname = main.main
}
Name
获取调用栈所用的函数的名字
func (f *Func) Name() string
利用反射获取Golang中函数的名字
package main
import (
"fmt"
"reflect"
"runtime"
"runtime/debug"
"strings"
)
func test() {
}
func main() {
fmt.Println(FuncName(test)) //main.test
fmt.Println(FuncName(debug.FreeOSMemory)) //runtime/debug.FreeOSMemory
fmt.Println(FuncName(debug.FreeOSMemory, '.')) //FreeOSMemory
fmt.Println(FuncName(debug.FreeOSMemory, '/')) //debug.FreeOSMemory
}
//FuncName 获取函数名称
func FuncName(i interface{}, seps ...rune) string {
//获取函数指针
pc := reflect.ValueOf(i).Pointer()
//获取函数名称
fn := runtime.FuncForPC(pc)
fname := fn.Name()
//使用seps进行分割
fields := strings.FieldsFunc(fname, func(sep rune) bool {
for _, item := range seps {
if item == sep {
return true
}
}
return false
})
//获取函数名称
if size := len(fields); size > 0 {
return fields[size-1]
}
return ""
}
runtime.GOMAXPROCS
- Go程序可以设置使用的CPU核心数量以发挥多核计算机的能力
在Golang程序运行时中实现了一个小型的任务调度器,其工作原理类似于操作系统的调度线程。
Go程序调度器可以高效地将CPU资源分配给每个任务,传统逻辑中开发者需要维护线程池中线程与CPU核心数量的对应关系。Golang通过runtime.GOMAXPROCS()
函数实现。
runtime.GOMAXPROCS(n int)//n 逻辑CPU数量
逻辑CPU数量 | 描述 |
---|---|
<1 | 不修改任何数值 |
=1 | 单核执行 |
>1 | 多核并发执行 |
可通过runtime.NumCPU()
函数来查询CPU数量,同时使用runtime.GOMAXPROCS()
函数来设置。
runtime.GOMAXPROCS(runtime.NumCPU())
runtime.Stack
runtime.Stack
将调用其的goroutine
的调用栈踪迹格式化后写入到buf
并返回写入的字节数,若all
参数为true
函数会在写入当前goroutine
的踪迹信息后,将其它所有goroutine
的调用栈踪迹都格式化写入到buf
中。
在调用Stack
方法后,首先会格式化当前goroutine
信息,然后把其他正在运行的goroutine
也格式化后写入buf
中。
func runtime.Stack(buf []byte, all bool) int
runtime.Gosched
runtime.Gosched()
用于将当前的goroutine
让出CPU时间片,让其它的goroutine
获得执行机会。同时,当前的goroutine
会在未来某个时间点继续运行。
类似于接力跑,当A跑了一会儿碰到runtime.Gosched()
代码就把接力棒交给B,A歇着了B继续跑。
例如:
package test
import (
"fmt"
"testing"
)
func test(n int) {
fmt.Printf("worker gorouotine %v\n", n)
}
func TestRuntime(t *testing.T) {
for i := 0; i < 3; i++ {
go test(i)
}
fmt.Printf("master goroutine\n")
}
$ go test -v -run TestRuntime runtime_test.go
=== RUN TestRuntime
master goroutine
--- PASS: TestRuntime (0.00s)
PASS
ok command-line-arguments 0.301s
多次执行发现,worker goroutine
有时并没有获得机会运行。
若添加runtime.GoSched()
后会发现全部都会打印出来
package test
import (
"fmt"
"runtime"
"testing"
)
func test(n int) {
fmt.Printf("worker gorouotine %v\n", n)
}
func TestRuntime(t *testing.T) {
for i := 0; i < 3; i++ {
go test(i)
}
runtime.Gosched()
fmt.Printf("master goroutine\n")
}
$ go test -v -run TestRuntime runtime_test.go
=== RUN TestRuntime
worker gorouotine 2
worker gorouotine 0
worker gorouotine 1
master goroutine
--- PASS: TestRuntime (0.00s)
PASS
ok command-line-arguments 0.296s
网友评论