runtime

作者: JunChow520 | 来源:发表于2021-04-22 02:10 被阅读0次

Golang生成的可执行文件在指定平台即可运行效率很高,它和C/C++一样编译出来的是二进制可执行文件,运行Go程序并不需要安装类似Java虚拟机之类的东西,是因为编译时Go会将runtime部分代码编译进去。

  • runtime类似Java中的JVM虚拟机,不过并非是虚拟机。
  • runtime核心功能包括并发调度、垃圾回收、内存管理...
Golang应用程序、runtime、可执行程序与操作系统之间的关系

系统信息

字段 描述
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

相关文章

网友评论

      本文标题:runtime

      本文链接:https://www.haomeiwen.com/subject/wmhxaktx.html