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