美文网首页Golang学习专题
运行时 runtime的神奇用法

运行时 runtime的神奇用法

作者: 酷走天涯 | 来源:发表于2018-12-26 17:22 被阅读87次

    runtime 包 提供了运行时与系统的交互,比如控制协程函数,触发垃圾立即回收等等底层操作,下面我们就运行时能做的所有事情逐个进行说明与代码演示

    • 1.获取GOROOT环境变量
    • 2.获取GO的版本号
    • 3.获取本机CPU个数
    • 4.设置最大可同时执行的最大CPU数
    • 5.设置cup profile 记录的速录
    • 6.查看cup profile 下一次堆栈跟踪数据
    • 7.立即执行一次垃圾回收
    • 8.给变量绑定方法,当垃圾回收的时候进行监听
    • 9.查看内存申请和分配统计信息
    • 10.查看程序正在使用的字节数
    • 11.查看程序正在使用的对象数
    • 12.获取调用堆栈列表
    • 13.获取内存profile记录历史
    • 14.执行一个断点
    • 15.获取程序调用go协程的栈踪迹历史
    • 16.获取当前函数或者上层函数的标识号、文件名、调用方法在当前文件中的行号
    • 17.获取与当前堆栈记录相关链的调用栈踪迹
    • 18.获取一个标识调用栈标识符pc对应的调用栈
    • 19.获取调用栈所调用的函数的名字
    • 20.获取调用栈所调用的函数的所在的源文件名和行号
    • 21.获取该调用栈的调用栈标识符
    • 22.获取当前进程执行的cgo调用次数
    • 23.获取当前存在的go协程数
    • 24.终止掉当前的go协程
    • 25.让其他go协程优先执行,等其他协程执行完后,在执行当前的协程
    • 26.获取活跃的go协程的堆栈profile以及记录个数
    • 27.将调用的go协程绑定到当前所在的操作系统线程,其它go协程不能进入该线程
    • 28.解除go协程与操作系统线程的绑定关系
    • 29.获取线程创建profile中的记录个数
    • 30.控制阻塞profile记录go协程阻塞事件的采样率
    • 31.返回当前阻塞profile中的记录个数

    1.获取GOROOT环境变量

    func GOROOT() string
    GOROOT返回Go的根目录。如果存在GOROOT环境变量,返回该变量的值;否则,返回创建Go时的根目录

    package main
    import (
        "fmt"
      "runtime"
    )
    func main() {
    fmt.Println(runtime.GOROOT())
    }
    

    image.png

    2.获取GO的版本号

    func Version() string
    返回Go的版本字符串。它要么是递交的hash和创建时的日期;要么是发行标签如"go1.3"

    package main
    
    import (
        "fmt"
      "runtime"
    )
    
    func main() {
    fmt.Println(runtime.Version())
    }
    

    image.png

    3.获取本机CPU个数

    func NumCPU() int
    NumCPU返回本地机器的逻辑CPU个数

    package main
    
    import (
      "fmt"
      "runtime"
    )
    
    func main() {
    fmt.Println(runtime.NumCPU())
    }
    

    image.png

    4.设置最大可同时执行的最大CPU数

    func GOMAXPROCS(n int) int

    GOMAXPROCS设置可同时执行的最大CPU数,并返回先前的设置。 若 n < 1,它就不会更改当前设置。本地机器的逻辑CPU数可通过 NumCPU 查询。本函数在调度程序优化后会去掉

    使用默认的cup数量 我的电脑是4核的

    package main
    
    import (
      "fmt"
      "time"
      )
    func main() {
    
       //runtime.GOMAXPROCS(1)
        startTime := time.Now()
        var s1 chan  int64 = make(chan int64)
        var s2 chan  int64 = make(chan int64)
        var s3 chan  int64 = make(chan int64)
        var s4 chan  int64 = make(chan int64)
        go calc(s1)
        go calc(s2)
        go calc(s3)
        go calc(s4)
        <-s1
        <-s2
        <-s3
        <-s4
        endTime := time.Now()
        fmt.Println(endTime.Sub(startTime))
        
    }
    func calc(s  chan int64) {
       var count int64 = 0
      for i := 0 ;i < 1000000000;i++ {
        count += int64(i)
      }
      s <- count
    }
    
    image.png

    下面我们将cup数量设置成1

    package main
    import (
      "fmt"
      "time"
      "runtime"
    )
    
    func main() {
       runtime.GOMAXPROCS(1)
        startTime := time.Now()
        var s1 chan  int64 = make(chan int64)
        var s2 chan  int64 = make(chan int64)
        var s3 chan  int64 = make(chan int64)
        var s4 chan  int64 = make(chan int64)
        go calc(s1)
        go calc(s2)
        go calc(s3)
        go calc(s4)
        <-s1
        <-s2
        <-s3
        <-s4
        endTime := time.Now()
        fmt.Println(endTime.Sub(startTime))
    
    }
    func calc(s  chan int64) {
       var count int64 = 0
      for i := 0 ;i < 1000000000;i++ {
        count += int64(i)
      }
      s <- count
    }
    
    image.png

    很明显速度慢了很多

    5.设置cup profile 记录的速录

    func SetCPUProfileRate(hz int)
    SetCPUProfileRate设置CPU profile记录的速率为平均每秒hz次。如果hz<=0,SetCPUProfileRate会关闭profile的记录。如果记录器在执行,该速率必须在关闭之后才能修改。

    绝大多数使用者应使用runtime/pprof包或testing包的-test.cpuprofile选项而非直接使用SetCPUProfileRate

    6.查看cup profile 下一次堆栈跟踪数据

    func CPUProfile() []byte
    目前已废弃

    7.立即执行一次垃圾回收

    func GC()
    GC执行一次垃圾回收

    看一下代码

    package main
    
    import (
       "runtime"
      "time"
    )
    type Student struct {
      name string
    
    }
    func main() {
      var i *Student = new(Student)
      runtime.SetFinalizer(i, func(i interface{}) {
       println("垃圾回收了")
      })
      runtime.GC()
      time.Sleep(time.Second)
    }
    
    image.png

    我们创建了一个指针类型的变量Student 当我们调用runtime.GC的时候,内存立即会回收,你可以把runtime.GC()屏蔽掉,程序就不在执行了


    8.给变量绑定方法,当垃圾回收的时候进行监听

    func SetFinalizer(x, f interface{})

    注意x必须是指针类型,f 函数的参数一定要和x保持一致,或者写interface{},不然程序会报错

    示例如下

    package main
    
    import (
       "runtime"
      "time"
    )
    type Student struct {
      name string
    
    }
    func main() {
      var i *Student = new(Student)
      runtime.SetFinalizer(i, func(i *Student) {
       println("垃圾回收了")
      })
      runtime.GC()
      time.Sleep(time.Second)
    }
    

    image.png

    9.查看内存申请和分配统计信息

    func ReadMemStats(m *MemStats)
    我们可以获得下面的信息

    type MemStats struct {
        // 一般统计
        Alloc      uint64 // 已申请且仍在使用的字节数
        TotalAlloc uint64 // 已申请的总字节数(已释放的部分也算在内)
        Sys        uint64 // 从系统中获取的字节数(下面XxxSys之和)
        Lookups    uint64 // 指针查找的次数
        Mallocs    uint64 // 申请内存的次数
        Frees      uint64 // 释放内存的次数
        // 主分配堆统计
        HeapAlloc    uint64 // 已申请且仍在使用的字节数
        HeapSys      uint64 // 从系统中获取的字节数
        HeapIdle     uint64 // 闲置span中的字节数
        HeapInuse    uint64 // 非闲置span中的字节数
        HeapReleased uint64 // 释放到系统的字节数
        HeapObjects  uint64 // 已分配对象的总个数
        // L低层次、大小固定的结构体分配器统计,Inuse为正在使用的字节数,Sys为从系统获取的字节数
        StackInuse  uint64 // 引导程序的堆栈
        StackSys    uint64
        MSpanInuse  uint64 // mspan结构体
        MSpanSys    uint64
        MCacheInuse uint64 // mcache结构体
        MCacheSys   uint64
        BuckHashSys uint64 // profile桶散列表
        GCSys       uint64 // GC元数据
        OtherSys    uint64 // 其他系统申请
        // 垃圾收集器统计
        NextGC       uint64 // 会在HeapAlloc字段到达该值(字节数)时运行下次GC
        LastGC       uint64 // 上次运行的绝对时间(纳秒)
        PauseTotalNs uint64
        PauseNs      [256]uint64 // 近期GC暂停时间的循环缓冲,最近一次在[(NumGC+255)%256]
        NumGC        uint32
        EnableGC     bool
        DebugGC      bool
        // 每次申请的字节数的统计,61是C代码中的尺寸分级数
        BySize [61]struct {
            Size    uint32
            Mallocs uint64
            Frees   uint64
        }
    }
    
    package main
    
    import (
       "runtime"
      "time"
      "fmt"
    )
    type Student struct {
      name string
    
    }
    func main() {
      var list = make([]*Student,0)
      for i:=0;i <100000 ;i++ {
        var s *Student = new(Student)
        list = append(list, s)
      }
      memStatus := runtime.MemStats{}
      runtime.ReadMemStats(&memStatus)
      fmt.Printf("申请的内存:%d\n",memStatus.Mallocs)
      fmt.Printf("释放的内存次数:%d\n",memStatus.Frees)
      time.Sleep(time.Second)
    }
    
    
    image.png

    10.查看程序正在使用的字节数

    func (r *MemProfileRecord) InUseBytes() int64
    InUseBytes返回正在使用的字节数(AllocBytes – FreeBytes)

    11.查看程序正在使用的对象数

    func (r *MemProfileRecord) InUseObjects() int64
    InUseObjects返回正在使用的对象数(AllocObjects - FreeObjects)

    12.获取调用堆栈列表

    func (r *MemProfileRecord) Stack() []uintptr
    Stack返回关联至此记录的调用栈踪迹,即r.Stack0的前缀。

    13.获取内存profile记录历史

    func MemProfile(p []MemProfileRecord, inuseZero bool) (n int, ok bool)
    MemProfile返回当前内存profile中的记录数n。若len(p)>=n,MemProfile会将此分析报告复制到p中并返回(n, true);如果len(p)<n,MemProfile则不会更改p,而只返回(n, false)。

    如果inuseZero为真,该profile就会包含无效分配记录(其中r.AllocBytes>0,而r.AllocBytes==r.FreeBytes。这些内存都是被申请后又释放回运行时环境的)。

    大多数调用者应当使用runtime/pprof包或testing包的-test.memprofile标记,而非直接调用MemProfile

    14.执行一个断点

    func Breakpoint()

    runtime.Breakpoint()
    
    image.png

    15.获取程序调用go协程的栈踪迹历史

    func Stack(buf []byte, all bool) int
    Stack将调用其的go程的调用栈踪迹格式化后写入到buf中并返回写入的字节数。若all为true,函数会在写入当前go程的踪迹信息后,将其它所有go程的调用栈踪迹都格式化写入到buf中。

    package main
    
    import (
         "time"
      "runtime"
      "fmt"
    )
    
    func main() {
      go showRecord()
      time.Sleep(time.Second)
      buf := make([]byte,10000)
      runtime.Stack(buf,true)
      fmt.Println(string(buf))
    }
    
    func showRecord(){
     tiker := time.Tick(time.Second)
     for t := range  tiker {
       fmt.Println(t)
     }
    }
    
    image.png

    我们在调用Stack方法后,首先格式化当前go协程的信息,然后把其他正在运行的go协程也格式化后写入buf中

    16.获取当前函数或者上层函数的标识号、文件名、调用方法在当前文件中的行号

    func Caller(skip int) (pc uintptr, file string, line int, ok bool)

    package main
    import (
      "runtime"
      "fmt"
    )
    
    func main() {
      pc,file,line,ok := runtime.Caller(0)
      fmt.Println(pc)
      fmt.Println(file)
      fmt.Println(line)
      fmt.Println(ok)
    }
    
    image.png

    pc = 17380971 不是main函数自己的标识 runtime.Caller 方法的标识,line = 13 标识它在main方法中的第13行被调用

    package main
    
    import (
      "runtime"
      "fmt"
    )
    
    func main() {
      pc,_,line,_ := runtime.Caller(1)
      fmt.Printf("main函数的pc:%d\n",pc)
      fmt.Printf("main函数被调用的行数:%d\n",line)
      show()
    }
    func show(){
      pc,_,line,_ := runtime.Caller(1)
      fmt.Printf("show函数的pc:%d\n",pc)
      fmt.Printf("show函数被调用的行数:%d\n",line)
      // 这个是main函数的栈
      pc,_,line,_ = runtime.Caller(2)
      fmt.Printf("show的上层函数的pc:%d\n",pc)
      fmt.Printf("show的上层函数被调用的行数:%d\n",line)
      pc,_,_,_ = runtime.Caller(3)
      fmt.Println(pc)
      pc,_,_,_ = runtime.Caller(4)
      fmt.Println(pc)
    }
    
    image.png

    通过上面的例子我演示了如何追踪一个方法被调用的顺序,以及所有相关函数的信息

    17.获取与当前堆栈记录相关链的调用栈踪迹

    func Callers(skip int, pc []uintptr) int

    函数把当前go程调用栈上的调用栈标识符填入切片pc中,返回写入到pc中的项数。实参skip为开始在pc中记录之前所要跳过的栈帧数,0表示Callers自身的调用栈,1表示Callers所在的调用栈。返回写入p的项数

    package main
    
    import (
      "runtime"
      "fmt"
    )
    
    
    func main() {
      pcs := make([]uintptr,10)
      i := runtime.Callers(1,pcs)
      fmt.Println(pcs[:i])
    }
    
    image.png

    我们获得了三个pc 其中有一个是main方法自身的

    18.获取一个标识调用栈标识符pc对应的调用栈

    func FuncForPC(pc uintptr) *Func

    package main
    
    import (
      "runtime"
      )
    
    
    func main() {
    
      pcs := make([]uintptr,10)
      i := runtime.Callers(1,pcs)
      for _,pc := range pcs[:i]{
        println(runtime.FuncForPC(pc))
      }
    }
    
    image.png

    我们知道这个调用栈有什么用呢?请继续下想看

    19.获取调用栈所调用的函数的名字

    func (f *Func) Name() string

    package main
    
    import (
      "runtime"
      )
    
    
    
    func main() {
    
      pcs := make([]uintptr,10)
      i := runtime.Callers(1,pcs)
      for _,pc := range pcs[:i]{
        funcPC := runtime.FuncForPC(pc)
        println(funcPC.Name())
      }
    }
    
    image.png

    20.获取调用栈所调用的函数的所在的源文件名和行号

    func (f *Func) FileLine(pc uintptr) (file string, line int)

    package main
    import (
      "runtime"
      )
    
    func main() {
      pcs := make([]uintptr,10)
      i := runtime.Callers(1,pcs)
      for _,pc := range pcs[:i]{
        funcPC := runtime.FuncForPC(pc)
        file,line := funcPC.FileLine(pc)
        println(funcPC.Name(),file,line)
      }
    }
    
    image.png

    21.获取该调用栈的调用栈标识符

    func (f *Func) Entry() uintptr

    package main
    
    import (
      "runtime"
      )
    
    
    
    func main() {
      pcs := make([]uintptr,10)
      i := runtime.Callers(1,pcs)
      for _,pc := range pcs[:i]{
        funcPC := runtime.FuncForPC(pc)
        println(funcPC.Entry())
      }
    }
    

    image.png

    22.获取当前进程执行的cgo调用次数

    func NumCgoCall() int64
    获取当前进程调用c方法的次数
    `

    package main
    
    import (
      "runtime"
      )
    /*
    #include <stdio.h>
    */
    import "C"
    
    
    
    
    func main() {
     println(runtime.NumCgoCall())
    }
    
    image.png
    注意我们没有调用c的方法为什么是1呢?因为import c是,会调用了c包中的init方法

    下面我们看一个完整例子

    import (
      "runtime"
      )
    /*
    #include <stdio.h>
    // 自定义一个c语言的方法
    static void myPrint(const char* msg) {
      printf("myPrint: %s", msg);
    }
    */
    import "C"
    
    
    
    
    func main() {
      // 调用c方法
      C.myPrint(C.CString("Hello,C\n"))
      println(runtime.NumCgoCall())
    }
    
    image.png

    23.获取当前存在的go协程数

    func NumGoroutine() int

    package main
    
    import "runtime"
    
    
    
    func main() {
     go print()
     print()
     println(runtime.NumGoroutine())
    }
    func print(){
    
    }
    

    image.png

    我们可以看到输出的是2 表示存在2个go协程 一个是main.go 另外一个是go print()

    24.终止掉当前的go协程

    func Goexit()

    package main
    import (
      "runtime"
        "fmt"
    )
    
    func main() {
     print()  // 1
     fmt.Println("继续执行")
    }
    func print(){
      fmt.Println("准备结束go协程")
      runtime.Goexit()
      defer fmt.Println("结束了")
    }
    

    image.png

    Goexit终止调用它的go协程,其他协程不受影响,Goexit会在终止该go协程前执行所有的defer函数,前提是defer必须在它前面定义,如果在main go协程调用本方法,会终止该go协程,但不会让main返回,因为main函数没有返回,程序会继续执行其他go协程,当其他go协程执行完毕后,程序就会崩溃

    25.让其他go协程优先执行,等其他协程执行完后,在执行当前的协程

    func Gosched()

    我们先看一个示例

    package main
    import (
      "fmt"
      )
    
    func main() {
      go print()  // 1
      fmt.Println("继续执行")
    }
    func print(){
      fmt.Println("执行打印方法")
    }
    
    image.png

    我们在1处调用了go print方法,但是还未执行 main函数就执行完毕了,因为两个协程是并发的

    那么我们应该怎么才能让每个协程都能够执行完毕呢?方法有很多种,不过就针对这个知识点,我们就使用 runtime.Gosched()来解决

    package main
    import (
      "fmt"
      "runtime"
    )
    
    func main() {
      go print()  // 1
      runtime.Gosched()
      fmt.Println("继续执行")
    }
    func print(){
      fmt.Println("执行打印方法")
    }
    

    image.png

    26.获取活跃的go协程的堆栈profile以及记录个数

    func GoroutineProfile(p []StackRecord) (n int, ok bool)

    
    

    27.将调用的go协程绑定到当前所在的操作系统线程,其它go协程不能进入该线程

    func LockOSThread()
    将调用的go程绑定到它当前所在的操作系统线程。除非调用的go程退出或调用UnlockOSThread,否则它将总是在该线程中执行,而其它go程则不能进入该线程

    我们看下面一个例子

    package main
    import (
      "fmt"
      "runtime"
      "time"
    )
    
    func main() {
      go calcSum1()
      go calcSum2()
      time.Sleep(time.Second*100)
    }
    
    func calcSum1(){
      runtime.LockOSThread()
      start := time.Now()
      count := 0
      for i := 0; i < 10000000000 ; i++  {
        count += i
      }
      end := time.Now()
      fmt.Println("calcSum1耗时")
      fmt.Println(end.Sub(start))
      defer runtime.UnlockOSThread()
    }
    
    func calcSum2(){
      start := time.Now()
      count := 0
      for i := 0; i < 10000000000 ; i++  {
        count += i
      }
      end := time.Now()
      fmt.Println("calcSum2耗时")
      fmt.Println(end.Sub(start))
    }
    
    image.png

    测试速度没有多大的差别,如果有需要协程,但是有一项重要的功能需要占一个核,就需要

    28.解除go协程与操作系统线程的绑定关系

    func UnlockOSThread()
    将调用的go程解除和它绑定的操作系统线程。若调用的go程未调用LockOSThread,UnlockOSThread不做操作

    29.获取线程创建profile中的记录个数

    func ThreadCreateProfile(p []StackRecord) (n int, ok bool)
    返回线程创建profile中的记录个数。如果len(p)>=n,本函数就会将profile中的记录复制到p中并返回(n, true)。若len(p)<n,则不会更改p,而只返回(n, false)。

    绝大多数使用者应当使用runtime/pprof包,而非直接调用ThreadCreateProfile。

    30.控制阻塞profile记录go协程阻塞事件的采样率

    func SetBlockProfileRate(rate int)

    SetBlockProfileRate控制阻塞profile记录go程阻塞事件的采样频率。对于一个阻塞事件,平均每阻塞rate纳秒,阻塞profile记录器就采集一份样本。

    要在profile中包括每一个阻塞事件,需传入rate=1;要完全关闭阻塞profile的记录,需传入rate<=0。

    31.返回当前阻塞profile中的记录个数

    func BlockProfile(p []BlockProfileRecord) (n int, ok bool)

    BlockProfile返回当前阻塞profile中的记录个数。如果len(p)>=n,本函数就会将此profile中的记录复制到p中并返回(n, true)。如果len(p)<n,本函数则不会修改p,而只返回(n, false)。

    绝大多数使用者应当使用runtime/pprof包或testing包的-test.blockprofile标记, 而非直接调用 BlockProfile

    相关文章

      网友评论

        本文标题:运行时 runtime的神奇用法

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