运行时 runtime的神奇用法

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容

  • runtime 包 提供了运行时与系统的交互,比如控制协程函数,触发垃圾立即回收等等底层操作,下面我们就运行时能做...
    酷走天涯阅读 458评论 0 0
  • 作者:Li_Mr 原文:开源中国博客 ​时至2018年的今天,C++ 在互联网服务端开发方向依然占据着相当大的份额...
    OSC开源社区阅读 21,715评论 2 32
  • 转载自:https://halfrost.com/go_map_chapter_one/ https://half...
    HuJay阅读 6,132评论 1 5
  • 软件包 pprof主要功能是可视化工具所期望的格式写入运行时的分析数据 获取所有已知profile的切片,按名称排...
    酷走天涯阅读 702评论 0 0
  • 亲爱的爸爸妈妈: 你们好!我是你们的宝贝女儿,很高兴在这里给你们写信,来感谢这十一年来对我的养育之恩。 随着一声声...
    王佳睿阅读 553评论 0 1