go-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()) }

2.獲取GO的版本號
func Version() string
返回Go的版本字符串。它要么是遞交的hash和創建時的日期;要么是發行標簽如"go1.3"
package main
import ( "fmt" "runtime" ) func main() { fmt.Println(runtime.Version()) }

3.獲取本機CPU個數
func NumCPU() int
NumCPU返回本地機器的邏輯CPU個數
package main
import ( "fmt" "runtime" ) func main() { fmt.Println(runtime.NumCPU()) }

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 }

下面我們將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 }

很明顯速度慢了很多
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) }

我們創建了一個指針類型的變量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) }

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) }

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()

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) } }

我們在調用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) }

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) }

通過上面的例子我演示了如何追蹤一個方法被調用的順序,以及所有相關函數的信息
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]) }

我們獲得了三個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)) } }

我們知道這個調用棧有什么用呢?請繼續下想看
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()) } }

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) } }

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()) } }

22.獲取當前進程執行的cgo調用次數
func NumCgoCall() int64
獲取當前進程調用c方法的次數
`
package main
import ( "runtime" ) /* #include <stdio.h> */ import "C" func main() { println(runtime.NumCgoCall()) }

注意我們沒有調用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()) }

23.獲取當前存在的go協程數
func NumGoroutine() int
package main
import "runtime" func main() { go print() print() println(runtime.NumGoroutine()) } func print(){ }

我們可以看到輸出的是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("結束了") }

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("執行打印方法") }

我們在1處調用了go print
方法,但是還未執行 main函數就執行完畢了,因為兩個協程是並發的
那么我們應該怎么才能讓每個協程都能夠執行完畢呢?方法有很多種,不過就針對這個知識點,我們就使用 runtime.Gosched()
來解決
package main
import ( "fmt" "runtime" ) func main() { go print() // 1 runtime.Gosched() fmt.Println("繼續執行") } func print(){ fmt.Println("執行打印方法") }

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)) }

測試速度沒有多大的差別,如果有需要協程,但是有一項重要的功能需要占一個線程,就需要它
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