手记

GO-ruantime


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

©著作权归作者所有:来自51CTO博客作者wx5ba3323aac676的原创作品,如需转载,请注明出处,否则将追究法律责任


0人推荐
随时随地看视频
慕课网APP