以下内容,是对 运行时 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()获取GOROOT环境变量
GOROOT() 返回Go的根目录。如果存在GOROOT环境变量,返回该变量的值;否则,返回创建Go时的根目录
1 2 3 4 5 6 7 8 9 10 package mainimport ( "fmt" "runtime" ) func main () { fmt.Println(runtime.GOROOT()) }
2. Version() 获取GO的版本号
Version() 返回Go的版本字符串。要么是提交的hash和创建时的日期;要么是发行标签如”go1.20”
1 2 3 4 5 6 7 8 9 10 package mainimport ( "fmt" "runtime" ) func main () { fmt.Println(runtime.Version()) }
3. NumCPU() 获取本机CPU个数
NumCPU返回本地机器的逻辑CPU个数
1 2 3 4 5 6 7 8 9 10 11 12 package mainimport ( "fmt" "runtime" ) func main () { fmt.Println(runtime.NumCPU()) }
4. GOMAXPROCS() 设置最大可同时执行的最大CPU数
GOMAXPROCS() 设置可同时执行的最大CPU数,并返回先前的设置。 若 n < 1,则不会更改当前设置。
本地机器的逻辑CPU数可通过 NumCPU 查询。该函数在调度程序优化后会去掉?(啥时候..)
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 package mainimport ( "fmt" "runtime" "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 }
5. SetCPUProfileRate() 设置cup profile 记录的速录
SetCPUProfileRate() 设置CPU profile记录的速率为平均每秒hz次。如果hz<=0,SetCPUProfileRate会关闭profile的记录。如果记录器在执行,该速率必须在关闭之后才能修改
绝大多数使用者应使用runtime/pprof包或testing包的-test.cpuprofile选项而非直接使用SetCPUProfileRate
6. CPUProfile 查看cup profile 下一次堆栈跟踪数据
func CPUProfile() []byte
已废弃
7. GC() 立即执行一次垃圾回收
Go三种触发GC的方式之一 (另外两种为2分钟固定一次 && 达到阈值时触发)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package mainimport ( "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. SetFinalizer() 给变量绑定方法,当垃圾回收的时候进行监听
func SetFinalizer(x, f interface{})
注意x必须是指针类型,f 函数的参数一定要和x保持一致,或者写interface{},不然程序会报错
代码同上例
9. 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 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 Lookups uint64 Mallocs uint64 Frees uint64 HeapAlloc uint64 HeapSys uint64 HeapIdle uint64 HeapInuse uint64 HeapReleased uint64 HeapObjects uint64 StackInuse uint64 StackSys uint64 MSpanInuse uint64 MSpanSys uint64 MCacheInuse uint64 MCacheSys uint64 BuckHashSys uint64 GCSys uint64 OtherSys uint64 NextGC uint64 LastGC uint64 PauseTotalNs uint64 PauseNs [256 ]uint64 NumGC uint32 EnableGC bool DebugGC bool BySize [61 ]struct { Size uint32 Mallocs uint64 Frees uint64 } }
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 package mainimport ( "fmt" "runtime" "time" ) type Student2 struct { name string } func main () { var list = make ([]*Student2, 0 ) for i := 0 ; i < 100000 ; i++ { var s *Student2 = new (Student2) 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) }
10. InUseBytes() 查看程序正在使用的字节数
func (r *MemProfileRecord) InUseBytes() int64
InUseBytes 返回正在使用的字节数(AllocBytes – FreeBytes)
11. InUseObjects() 查看程序正在使用的对象数
func (r *MemProfileRecord) InUseObjects() int64
InUseObjects 返回正在使用的对象数(AllocObjects - FreeObjects)
12. Stack() 获取调用堆栈列表
func (r *MemProfileRecord) Stack() []uintptr
Stack返回关联至此记录的调用栈踪迹,即r.Stack0的前缀
13. MemProfile() 获取内存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为true,该profile就会包含无效分配记录(其中r.AllocBytes>0,而r.AllocBytes==r.FreeBytes。这些内存都是被申请后又释放回运行时环境的)
大多数调用者应当使用runtime/pprof包或testing包的-test.memprofile标记,而非直接调用MemProfile
14. Breakpoint() 执行一个断点
runtime.Breakpoint()
15. Stack() 获取程序调用go协程的栈踪迹历史
func Stack(buf []byte, all bool) int
Stack 将调用其的go程的调用栈踪迹格式化后写入到buf中并返回写入的字节数
若all为true,函数会在写入当前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 mainimport ( "fmt" "runtime" "time" ) func main () { go showRecord() time.Sleep(time.Second) buf := make ([]byte , 10000000000 ) runtime.Stack(buf, true ) fmt.Println(string (buf)) } func showRecord () { ticker := time.Tick(time.Second) for t := range ticker { fmt.Println(t) } }
输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 2023 -04 -19 17 :25 :26.386522 +0800 CST m=+1.001105543 2023 -04 -19 17 :25 :27.386892 +0800 CST m=+2.001489376 2023 -04 -19 17 :25 :28.386505 +0800 CST m=+3.001116043 2023 -04 -19 17 :25 :29.38553 +0800 CST m=+4.000154334 2023 -04 -19 17 :25 :30.385618 +0800 CST m=+5.000255584 2023 -04 -19 17 :25 :31.385817 +0800 CST m=+6.000468334 2023 -04 -19 17 :25 :32.385528 +0800 CST m=+7.000192918 2023 -04 -19 17 :25 :33.385646 +0800 CST m=+8.000323543 goroutine 1 [running]: main.main() /Users/fliter/runtime-demo/15 Stack.go :13 +0x68 goroutine 4 [chan receive]: main.showRecord() /Users/fliter/runtime-demo/15 Stack.go :19 +0xac created by main.main /Users/fliter/runtime-demo/15 Stack.go :10 +0x24
16. Caller() 获取当前函数或者上层函数的标识号、文件名、调用方法在当前文件中的行号
func Caller(skip int) (pc uintptr, file string, line int, ok bool)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package mainimport ( "fmt" "runtime" ) func main () { pc, file, line, ok := runtime.Caller(0 ) fmt.Println(pc) fmt.Println(file) fmt.Println(line) fmt.Println(ok) }
pc = 4336410771 不是main函数的标识,而是runtime.Caller 方法的标识,line = 13 标识它在main方法中的第13行被调用
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 41 42 package mainimport ( "fmt" "runtime" ) 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) 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) }
golang获取调用者的方法名及所在行数
runtime.Caller的性能问题
17. 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 mainimport ( "fmt" "runtime" ) func main () { pcs := make ([]uintptr , 10 ) i := runtime.Callers(1 , pcs) fmt.Println(pcs[:i]) }
获得了三个pc 其中有一个是main方法自身的
18. FuncForPC() 获取一个标识调用栈标识符pc对应的调用栈
func FuncForPC(pc uintptr) *Func
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package mainimport ( "runtime" ) func main () { pcs := make ([]uintptr , 10 ) i := runtime.Callers(1 , pcs) for _, pc := range pcs[:i] { println (runtime.FuncForPC(pc)) } }
输出:
1 2 3 0x102f660f0 0x102f5c9f0 0x102f64840
用途见下
19. Name() 获取调用栈所调用的函数的名字
func (f *Func) string
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport ( "runtime" ) func main () { pcs := make ([]uintptr , 10 ) i := runtime.Callers(1 , pcs) for _, pc := range pcs[:i] { funcPC := runtime.FuncForPC(pc) println (funcPC.Name()) } }
输出:
1 2 3 main.main runtime.main runtime.goexit
20. FileLine() 获取调用栈所调用的函数的所在的源文件名和行号
func (f *Func) FileLine(pc uintptr) (file string, line int)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport ( "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) } }
输出:
1 2 3 main.main /Users/fliter/runtime-demo/20 FileLine.go 9 runtime.main /Users/fliter/.g/go /src/runtime/proc.go 259 runtime.goexit /Users/fliter/.g/go /src/runtime/asm_arm64.s 1166
21. Entry() 获取该调用栈的调用栈标识符
func (f *Func) Entry() uintptr
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package mainimport ( "runtime" ) func main () { pcs := make ([]uintptr , 10 ) i := runtime.Callers(1 , pcs) for _, pc := range pcs[:i] { funcPC := runtime.FuncForPC(pc) println (funcPC.Entry()) } }
输出:
1 2 3 4310699120 4310540672 4310690704
22. NumCgoCall() 获取当前进程执行的cgo调用次数
获取当前进程调用C方法的次数
func NumCgoCall() int64
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package mainimport ( "runtime" ) import "C" func main () { println (runtime.NumCgoCall()) }
没有调用C的方法为什么是1呢?因为 import C 会调用C包中的init方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package mainimport ( "runtime" ) import "C" func main () { C.myPrint(C.CString("Hello,C\n" )) println (runtime.NumCgoCall()) }
23. NumGoroutine() 获取当前存在的go协程数
func NumGoroutine() int
1 2 3 4 5 6 7 8 9 10 11 12 package mainimport "runtime" func main () { go print () print () println (runtime.NumGoroutine()) } func print () {}
当前程序有 2个go协程 一个是main.go主协程, 另外一个是 go print()
24. Goexit() 终止掉当前的go协程
func Goexit()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package mainimport ( "fmt" "runtime" ) func main () { print () fmt.Println("继续执行" ) } func print () { fmt.Println("准备结束go协程" ) runtime.Goexit() defer fmt.Println("结束了" ) }
输出:
1 2 3 准备结束go 协程 fatal error : no goroutines (main called runtime.Goexit) - deadlock! exit status 2
Goexit终止调用它的go协程,其他协程不受影响,Goexit会在终止该go协程前执行所有的defer函数,前提是defer必须在它前面定义,如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package mainimport ( "fmt" "runtime" ) func main () { print () fmt.Println("继续执行" ) } func print () { fmt.Println("准备结束go协程" ) defer fmt.Println("结束了--会输出出来" ) runtime.Goexit() }
输出:
1 2 3 4 准备结束go 协程 结束了--会输出出来 fatal error : no goroutines (main called runtime.Goexit) - deadlock! exit status 2
如果在 main主协程调用该方法,会终止 主协程,但不会让main返回,因为main函数没有返回,
程序会继续执行其他go协程,当其他go协程执行完毕后,程序就会崩溃
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package mainimport ( "fmt" "runtime" "time" ) func main () { start := time.Now() go func () { time.Sleep(3e9 ) println ("123" ) }() defer fmt.Println(time.Since(start)) runtime.Goexit() }
输出:
1 2 3 4 20.5 µs123 fatal error : no goroutines (main called runtime.Goexit) - deadlock! exit status 2
25. Gosched() 让其他go协程优先执行,等其他协程执行完后,再执行当前的协程
func Gosched()
1 2 3 4 5 6 7 8 9 10 11 12 13 package mainimport ( "fmt" ) func main () { go print25() fmt.Println("继续执行" ) } func print25 () { fmt.Println("执行打印方法" ) }
调用了go print方法,但是还未执行, main函数就执行完毕了(启一个协程也是需要时间的,这个时间比for循环,比程序继续执行要耗时多很多)
可以使用channel,waitgroup等,此处使用 runtime.Gosched()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport ( "fmt" "runtime" ) func main () { go print25() runtime.Gosched() fmt.Println("继续执行" ) } func print25 () { fmt.Println("执行打印方法" ) }
输出:
Rust vs Go:常用语法对比(13)-将优先权让给其他线程
Go用两个协程交替打印100以内的奇偶数
26. GoroutineProfile() 获取活跃的go协程的堆栈profile以及记录个数
func GoroutineProfile(p []StackRecord) (n int, ok bool)
1 2 3 4 5 6 7 8 9 10 func GoroutineProfile (p []StackRecord) (n int , ok bool ) { return goroutineProfileWithLabels(p, nil ) }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package mainimport ( "fmt" "runtime" ) func main () { for i := 0 ; i < 10 ; i++ { go func (k int ) { fmt.Println(i) }(i) } fmt.Println("----------------" ) p := make ([]runtime.StackRecord, 10000 ) fmt.Println(runtime.GoroutineProfile(p)) }
输出:
1 2 3 4 5 6 7 8 9 10 11 12 10 10 10 10 10 10 10 ---------------- 7 10 10 1 true
pprof里面使用了此func
Go 应用的性能优化
27. LockOSThread() 将调用的go协程绑定到当前所在的操作系统线程,其它go协程不能进入该线程
func 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 37 package mainimport ( "fmt" "runtime" "time" ) func main () { go calcSum1() go calcSum2() time.Sleep(time.Second * 10 ) } 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)) }
输出:
1 2 3 4 calcSum1耗时 3.295679583 scalcSum2耗时 3.296763125 s
看起来没有太大的差别;
但估计在很多个协程(涉及到频繁的调度和切换),但是有一项重要功能需独占一个核,可使用该func
Go LockOSThread
28. UnlockOSThread() 解除go协程与操作系统线程的绑定关系
func UnlockOSThread()
将调用此func的协程,解除和其绑定的操作系统线程
若调用的协程未调用LockOSThread,UnlockOSThread不做操作
从1,10之后,调用了多少次LockOSThread,就要使用UnlockOSThread接触绑定..
29. ThreadCreateProfile() 获取线程创建profile中的记录个数
func ThreadCreateProfile(p []StackRecord) (n int, ok bool)
返回线程创建profile中的记录个数。
如果len(p)>=n,本func就会将profile中的记录复制到p中并返回(n, true)
若len(p)<n,则不会更改p,而只返回(n, false)
绝大多数情况下应当使用runtime/pprof包,而非直接调用ThreadCreateProfile
30. SetBlockProfileRate() 控制阻塞profile记录go协程阻塞事件的采样率
func SetBlockProfileRate(rate int)
SetBlockProfileRate 控制阻塞profile记录go程阻塞事件的采样频率。对于一个阻塞事件,平均每阻塞rate纳秒,阻塞profile记录器就采集一份样本。
要在profile中包括每一个阻塞事件,需传入rate=1
要完全关闭阻塞profile的记录,需传入rate<=0
31. BlockProfile() 返回当前阻塞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
原文链接: https://dashen.tech/2022/05/19/Go-Runtime功能初探/
版权声明: 转载请注明出处.