在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
runtime 包 提供了运行时与系统的交互,比如控制协程函数,触发垃圾立即回收等等底层操作,下面我们就运行时能做的所有事情逐个进行说明与代码演示
1.获取GOROOT环境变量
GOROOT返回Go的根目录。如果存在GOROOT环境变量,返回该变量的值;否则,返回创建Go时的根目录 package main
import (
"fmt"
"runtime"
)
func main() {
fmt.Println(runtime.GOROOT())
}
image.png
2.获取GO的版本号
返回Go的版本字符串。它要么是递交的hash和创建时的日期;要么是发行标签如"go1.3" package main
import (
"fmt"
"runtime"
)
func main() {
fmt.Println(runtime.Version())
}
image.png
3.获取本机CPU个数
NumCPU返回本地机器的逻辑CPU个数 package main
import (
"fmt"
"runtime"
)
func main() {
fmt.Println(runtime.NumCPU())
}
image.png
4.设置最大可同时执行的最大CPU数
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 记录的速录
SetCPUProfileRate设置CPU profile记录的速率为平均每秒hz次。如果hz<=0,SetCPUProfileRate会关闭profile的记录。如果记录器在执行,该速率必须在关闭之后才能修改。 绝大多数使用者应使用runtime/pprof包或testing包的-test.cpuprofile选项而非直接使用SetCPUProfileRate 6.查看cup profile 下一次堆栈跟踪数据
目前已废弃 7.立即执行一次垃圾回收
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的时候,内存立即会回收,你可以把 8.给变量绑定方法,当垃圾回收的时候进行监听
注意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.查看内存申请和分配统计信息
我们可以获得下面的信息 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.查看程序正在使用的字节数
InUseBytes返回正在使用的字节数(AllocBytes – FreeBytes) 11.查看程序正在使用的对象数
InUseObjects返回正在使用的对象数(AllocObjects - FreeObjects) 12.获取调用堆栈列表
Stack返回关联至此记录的调用栈踪迹,即r.Stack0的前缀。 13.获取内存profile记录历史
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()
image.png
15.获取程序调用go协程的栈踪迹历史
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
我们在调用 16.获取当前函数或者上层函数的标识号、文件名、调用方法在当前文件中的行号
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函数自己的标识 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.获取与当前堆栈记录相关链的调用栈踪迹
函数把当前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对应的调用栈
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.获取调用栈所调用的函数的名字
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.获取调用栈所调用的函数的所在的源文件名和行号
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.获取该调用栈的调用栈标识符
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调用次数
获取当前进程调用c方法的次数 ` package main
import (
"runtime"
)
/*
#include <stdio.h>
*/
import "C"
func main() {
println(runtime.NumCgoCall())
}
image.png
注意我们没有调用c的方法为什么是1呢?因为 下面我们看一个完整例子 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协程数
package main
import "runtime"
func main() {
go print()
print()
println(runtime.NumGoroutine())
}
func print(){
}
image.png
我们可以看到输出的是2 表示存在2个go协程 一个是 24.终止掉当前的go协程
package main
import (
"runtime"
"fmt"
)
func main() {
print() // 1
fmt.Println("继续执行")
}
func print(){
fmt.Println("准备结束go协程")
runtime.Goexit()
defer fmt.Println("结束了")
}
image.png
25.让其他go协程优先执行,等其他协程执行完后,在执行当前的协程
我们先看一个示例 package main
import (
"fmt"
)
func main() {
go print() // 1
fmt.Println("继续执行")
}
func print(){
fmt.Println("执行打印方法")
}
image.png
我们在1处调用了 那么我们应该怎么才能让每个协程都能够执行完毕呢?方法有很多种,不过就针对这个知识点,我们就使用 package main
import (
"fmt"
"runtime"
)
func main() {
go print() // 1
runtime.Gosched()
fmt.Println("继续执行")
}
func print(){
fmt.Println("执行打印方法")
}
image.png
26.获取活跃的go协程的堆栈profile以及记录个数
27.将调用的go协程绑定到当前所在的操作系统线程,其它go协程不能进入该线程
将调用的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))
}
|
请发表评论