16. Go runtime详解

runtime 包 提供了运行时与系统的交互,比如控制协程函数,触发垃圾立即回收等等底层操作;

1 runtime.GOARCH

获取 GOARCH 信息

1
fmt.Println(runtime.GOARCH)   // arm64

2 runtime.GOOS

获取 GOOS 信息

1
fmt.Println(runtime.GOOS)     // darwin

3 runtime.GOROOT()

获取goroot环境变量 func GOROOT() string

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package main

import (
	"fmt"
	"runtime"
)

func main() {
	fmt.Println(runtime.GOROOT()) // /Users/liusaisai/.g/go
	fmt.Println(runtime.GOARCH)   // arm64
	fmt.Println(runtime.GOOS)     // darwin
}

4 runtime.Version()

获取go版本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package main

import (
	"fmt"
	"runtime"
)

func main() {
	fmt.Println(runtime.Version())  //go1.18

}

5 runtime.NumCPU()

获取机器cpu数量

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
package main

import (
	"fmt"
	"runtime"
)

func main() {
	fmt.Println(runtime.NumCPU())  // 16
}

6 runtime.GOMAXPROCS(n int)

GOMAXPROCS设置可同时执行的最大CPU数,并返回先前的设置。 若 n < 1,它就不会更改当前设置。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
package main

import (
	"fmt"
	"runtime"
)

func main() {
	fmt.Println(runtime.GOMAXPROCS(16))
}

7 runtime.SetFinalizer()

给变量绑定方法,当垃圾回收的时候进行监听 SetFinalizer(x, f interface{})

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
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)
}

8 runtime.GC()

进行垃圾回收

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
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)
}

9 runtime.ReadMemStats()

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
	"fmt"
	"runtime"
	"time"
)

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)
}
  • memStatus中可以查看到的信息

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    
    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
        }
    }

10 runtime.InUseBytes()

InUseBytes返回正在使用的字节数(AllocBytes – FreeBytes)

11 runtime.InUseObjects()

InUseObjects返回正在使用的对象数(AllocObjects - FreeObjects)

12 runtime.Stack()

Stack返回关联至此记录的调用栈踪迹,即r.Stack0的前缀。

13 runtime.MemProfile()

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 runtime.Breakpoint()

执行一个断点

15 runtime.Stack()

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

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import (
	"fmt"
	"runtime"
	"time"
)

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-20221229160859663

16 runtime.Caller()

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
package main

import (
	"fmt"
	"runtime"
)

func main() {
	pc, file, line, ok := runtime.Caller(0)
	fmt.Println(pc)   // 4308282211
	fmt.Println(file) // /Users/liusaisai/workspace/goProject/src/picturePro/main.go
	fmt.Println(line) // 9
	fmt.Println(ok)   // ok
}

17 runtime.Callers()

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

函数把当前go程序调用栈上的调用栈标识符填入切片pc中,返回写入到pc中的项数。

实参skip为开始在pc中记录之前所要跳过的栈帧数,0表示Callers自身的调用栈,1表示Callers所在的调用栈。返回写入p的项数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package main

import (
	"fmt"
	"runtime"
)

func main() {
	pcs := make([]uintptr, 10)
	i := runtime.Callers(1, pcs)
	fmt.Println(pcs[:i])   // [4371884917 4371531008 4371698004]   三个pc 其中有一个是main方法自身的
}

18 runtime.FuncForPC()

func FuncForPC(pc uintptr) *Func

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import (
	"fmt"
	"runtime"
)

func main() {
	pcs := make([]uintptr, 10)
	i := runtime.Callers(1, pcs)
	fmt.Println(pcs[:i])
	for _, pc := range pcs[:i] {
		p := runtime.FuncForPC(pc)
		file, line := p.FileLine(pc)
		println(
			fmt.Sprintf("调用栈:%v ", p),
			fmt.Sprintf("调用函数名称:%v ", p.Name()),
			fmt.Sprintf("文件名:%v 行号:%v ", file, line),
			fmt.Sprintf("调用栈标识符:%v ", p.Entry()),
		)
	}
}
```
[4305863597 4305503488 4305670692]
调用栈:&{{}}  调用函数名称:main.main  文件名:/Users/liusaisai/workspace/goProject/src/picturePro/main.go 行号:10  调用栈标识符:4305863536 
调用栈:&{{}}  调用函数名称:runtime.main  文件名:/Users/liusaisai/.g/go/src/runtime/proc.go 行号:259  调用栈标识符:4305502896 
调用栈:&{{}}  调用函数名称:runtime.goexit  文件名:/Users/liusaisai/.g/go/src/runtime/asm_arm64.s 行号:1260  调用栈标识符:4305670688 
```

19 runtime.NumCgoCall()

获取当前进程调用c方法的次数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
package main

import (
	"runtime"
)

/*
#include <stdio.h>
*/
import "C"   // import c   调用了c包中的init方法

func main() {
	println(runtime.NumCgoCall())  // 1
}

20 runtime.NumGoroutine()

获取当前存在的go协程数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package main

import "runtime"

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

}

21 runtime.Goexit()

终止掉当前的go协程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package main
import (
  "runtime"
    "fmt"
)

func main() {
 print()  // 1
 fmt.Println("继续执行")
}
func print(){
  fmt.Println("准备结束go协程")
  runtime.Goexit()
  defer fmt.Println("结束了")
}
  • Goexit终止调用它的go协程,其他协程不受影响,Goexit会在终止该go协程前执行所有的defer函数,前提是defer必须在它前面定义,如果在main go协程调用本方法,会终止该go协程,但不会让main返回,因为main函数没有返回,程序会继续执行其他go协程,当其他go协程执行完毕后,程序就会崩溃

22 runtime.Gosched()

让出调度执行权限,让其他go协程优先执行, 等其他协程执行完后,在执行当前的协程

23 runtime.GoroutineProfile()

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

24 runtime.LockOSThread()

  • 将调用的go程绑定到它当前所在的操作系统线程。除非调用的go程退出或调用UnlockOSThread,否则它将总是在该线程中执行,而其它go程则不能进入该线程
  • 如果有需要协程,但是有一项重要的功能需要占一个线程,就需要它
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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))
}

25 runitme.UnlockOSThread()

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

26 runtime.ThreadCreateProfile()

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

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

27 runtime.SetCPUProfileRate(hz int)

官方注释:

  • SetCPUProfileRate设置CPU profile记录的速率为平均每秒hz次。如果hz<=0,SetCPUProfileRate会关闭profile的记录。如果记录器在执行,该速率必须在关闭之后才能修改。
  • 绝大多数使用者应使用runtime/pprof包或testing包的-test.cpuprofile选项而非直接使用SetCPUProfileRate

28 runtime.SetBlockProfileRate(rate int)

官方解释:

  • SetBlockProfileRate控制阻塞profile记录go程阻塞事件的采样频率。对于一个阻塞事件,平均每阻塞rate纳秒,阻塞profile记录器就采集一份样本。
  • 要在profile中包括每一个阻塞事件,需传入rate=1;要完全关闭阻塞profile的记录,需传入rate<=0。

29 runtime.BlockProfile()

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

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

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

Buy me a coffee~
Fred 支付宝支付宝
Fred 微信微信
0%