runtime.Gosched()表示讓CPU把時間片讓給別人,下次某個時候繼續恢復執行該goroutinegit
import ( "fmt" "runtime" ) func main() { go say("world") say("hello") } func say(s string) { for i := 0; i < 5; i++ { runtime.Gosched() fmt.Println(s) } }
輸出:github
hello world hello world hello world hello world hello
查看web
println(runtime.Version()) // go1.4.1 println(runtime.NumGoroutine()) // 2 println(runtime.NumCPU()) // 4 println(runtime.GOMAXPROCS(-1)) // 1
func init() { numcpu := runtime.NumCPU() runtime.GOMAXPROCS(numcpu) // 嘗試使用全部可用的CPU }
defer debug.SetGCPercent(debug.SetGCPercent(-1))
runtime.GC()
GODEBUG=gctrace=1 ./test_server
將gc信息保存到文件:bash
GODEBUG=gctrace=1 go run main.go 2> gctrace.log
可視化信息 https://github.com/davecheney/gcvis
閉包
GODEBUG=gctrace=1 ./test_server 2>&1 | gcvis
go func() { fmt.Println("i am a goroutine") time.Sleep(time.Second) }() time.Sleep(500 * time.Millisecond) buf := make([]byte, 1024) n := runtime.Stack(buf, false) fmt.Println(string(buf[:n])) fmt.Println("===================") n = runtime.Stack(buf, true) fmt.Println(string(buf[:n]))
第一個輸出:函數
goroutine 1 [running]: main.main() /項目路徑/src/Test/Test.go:18 +0xa5
第二個輸出:測試
goroutine 1 [running]: main.main() /項目路徑/src/Test/Test.go:23 +0x2c2 goroutine 17 [sleep]: time.Sleep(0x3b9aca00) /usr/local/go/src/runtime/time.go:59 +0xf9 main.main.func1() /項目路徑/src/Test/Test.go:12 +0xd9 created by main.main /項目路徑/src/Test/Test.go:13 +0x37
data := debug.Stack()
輸出:ui
/項目路徑/src/test/test.go:17 (0x400c49) main: data := debug.Stack() /go安裝路徑/src/runtime/proc.go:111 (0x42846f) main: main_main() /go安裝路徑/src/runtime/asm_amd64.s:1696 (0x454471) goexit: BYTE $0x90 // NOP
函數的簽名以下:spa
func runtime.Caller(skip int) (pc uintptr, file string, line int, ok bool)
runtime.Caller 返回當前 goroutine 的棧上的函數調用信息. 主要有當前的 pc 值和調用的文件和行號等信息. 若沒法得到信息, 返回的 ok 值爲 false.debug
其輸入參數 skip 爲要跳過的棧幀數, 若爲 0 則表示 runtime.Caller 的調用者.
注意:因爲歷史緣由, runtime.Caller 和 runtime.Callers 中的 skip 含義並不相同, 後面會講到.
下面是一個簡單的例子, 打印函數調用的棧幀信息:
package main import ( "fmt" "runtime" ) func main() { fun1() } func fun1() { for skip := 0; ; skip++ { pc, file, line, ok := runtime.Caller(skip) if !ok { break } fmt.Printf("skip = %v, pc = %v, file = %v, line = %v\n", skip, pc, file, line) } }
輸出結果:
skip = 0, pc = 8274, file = /項目路徑/src/test/main.go, line = 14 skip = 1, pc = 8219, file = /項目路徑/src/test/main.go, line = 9 skip = 2, pc = 77123, file = /usr/local/go/src/runtime/proc.go, line = 63 skip = 3, pc = 227809, file = /usr/local/go/src/runtime/asm_amd64.s, line = 2232
其中 skip = 0 爲當前文件的 main.main 函數, 以及對應的行號.
另外的 skip = 1 和 skip = 2 也分別對應2個函數調用. 經過查閱 runtime/proc.c 文件的代碼, 咱們能夠知道對應的函數分別爲 runtime.main 和 runtime.goexit.
整理以後能夠知道, Go的普通程序的啓動順序以下:
函數的簽名以下:
func runtime.Callers(skip int, pc []uintptr) int
runtime.Callers 函數和 runtime.Caller 函數雖然名字類似(多一個後綴s), 可是函數的參數/返回值和參數的意義都有很大的差別.
runtime.Callers 把調用它的函數Go程棧上的程序計數器填入切片 pc 中. 參數 skip 爲開始在 pc 中記錄以前所要跳過的棧幀數, 若爲0則表示 runtime.Callers 自身的棧幀, 若爲1則表示調用者的棧幀. 該函數返回寫入到 pc 切片中的項數(受切片的容量限制).
下面是 runtime.Callers 的例子, 用於輸出每一個棧幀的 pc 信息:
func main() { fun1() } func fun1() { pc := make([]uintptr, 1024) for skip := 0; ; skip++ { n := runtime.Callers(skip, pc) if n <= 0 { break } fmt.Printf("skip = %v, pc = %v\n", skip, pc[:n]) } }
輸出:
skip = 0, pc = [28854 8368 8219 77155 227841] skip = 1, pc = [8368 8219 77155 227841] skip = 2, pc = [8219 77155 227841] skip = 3, pc = [77155 227841] skip = 4, pc = [227841]
輸出新的 pc 長度和 skip 大小有逆相關性. skip = 0 爲 runtime.Callers 自身的信息.
這個例子比前一個例子多輸出了一個棧幀, 就是由於多了一個runtime.Callers棧幀的信息(前一個例子是沒有runtime.Caller信息的(注意:沒有s後綴)).
那麼 runtime.Callers 和 runtime.Caller 有哪些關聯和差別?
由於前面2個例子爲不一樣的程序, 輸出的 pc 值並不具有參考性. 如今咱們看看在同一個例子的輸出結果如何:
package main import ( "fmt" "runtime" ) func main() { fun1() } func fun1() { for skip := 0; ; skip++ { pc, file, line, ok := runtime.Caller(skip) if !ok { break } fmt.Printf("skip = %v, pc = %v, file = %v, line = %v\n", skip, pc, file, line) } pc := make([]uintptr, 1024) for skip := 0; ; skip++ { n := runtime.Callers(skip, pc) if n <= 0 { break } fmt.Printf("skip = %v, pc = %v\n", skip, pc[:n]) } }
輸出:
skip = 0, pc = 8277, file = /項目路徑/src/test/main.go, line = 14 skip = 1, pc = 8219, file = /項目路徑/src/test/main.go, line = 9 skip = 2, pc = 78179, file = /usr/local/go/src/runtime/proc.go, line = 63 skip = 3, pc = 228865, file = /usr/local/go/src/runtime/asm_amd64.s, line = 2232 skip = 0, pc = [29878 8449 8219 78179 228865] skip = 1, pc = [8449 8219 78179 228865] skip = 2, pc = [8219 78179 228865] skip = 3, pc = [78179 228865] skip = 4, pc = [228865]
好比輸出結果能夠發現, 8219 78179 228865 這個 pc 值是相同的. 它們分別對應 main.main, runtime.main 和 runtime.goexit 函數.
runtime.Caller 輸出的 8277 和 runtime.Callers 輸出的 8449 並不相同. 這是由於, 這兩個函數的調用位置並不相同, 所以致使了 pc 值也不徹底相同.
最後就是 runtime.Callers 多輸出一個 29878 值, 對應runtime.Callers內部的調用位置.
因爲Go語言(Go1.2)採用分段堆棧, 所以不一樣的 pc 之間的大小關係並不明顯.
函數的簽名以下:
func runtime.FuncForPC(pc uintptr) *runtime.Func func (f *runtime.Func) FileLine(pc uintptr) (file string, line int) func (f *runtime.Func) Entry() uintptr func (f *runtime.Func) Name() string
其中 runtime.FuncForPC 返回包含給定 pc 地址的函數, 若是是無效 pc 則返回 nil .
runtime.Func.FileLine 返回與 pc 對應的源碼文件名和行號. 安裝文檔的說明, 若是pc不在函數幀範圍內, 則結果是不肯定的.
runtime.Func.Entry 對應函數的地址. runtime.Func.Name 返回該函數的名稱.
下面是 runtime.FuncForPC 的例子:
package main import ( "fmt" "runtime" ) func main() { fun1() } func fun1() { for skip := 0; ; skip++ { pc, _, _, ok := runtime.Caller(skip) if !ok { break } p := runtime.FuncForPC(pc) file, line := p.FileLine(0) fmt.Printf("skip = %v, pc = %v\n", skip, pc) fmt.Printf(" file = %v, line = %d\n", file, line) fmt.Printf(" entry = %v\n", p.Entry()) fmt.Printf(" name = %v\n", p.Name()) } fmt.Println("-------------------------") pc := make([]uintptr, 1024) for skip := 0; ; skip++ { n := runtime.Callers(skip, pc) if n <= 0 { break } fmt.Printf("skip = %v, pc = %v\n", skip, pc[:n]) for j := 0; j < n; j++ { p := runtime.FuncForPC(pc[j]) file, line := p.FileLine(0) fmt.Printf(" skip = %v, pc = %v\n", skip, pc[j]) fmt.Printf(" file = %v, line = %d\n", file, line) fmt.Printf(" entry = %v\n", p.Entry()) fmt.Printf(" name = %v\n", p.Name()) } break } }
輸出:
skip = 0, pc = 8277 file = /項目路徑/src/test/main.go, line = 12 entry = 8224 name = main.fun1 skip = 1, pc = 8219 file = /項目路徑/src/test/main.go, line = 8 entry = 8192 name = main.main skip = 2, pc = 80579 file = /usr/local/go/src/runtime/proc.go, line = 16 entry = 80336 name = runtime.main skip = 3, pc = 231265 file = /usr/local/go/src/runtime/asm_amd64.s, line = 2232 entry = 231264 name = runtime.goexit ------------------------- skip = 0, pc = [32278 8634 8219 80579 231265] skip = 0, pc = 32278 file = /usr/local/go/src/runtime/extern.go, line = 134 entry = 32192 name = runtime.Callers skip = 0, pc = 8634 file = /項目路徑/src/test/main.go, line = 12 entry = 8224 name = main.fun1 skip = 0, pc = 8219 file = /項目路徑/src/test/main.go, line = 8 entry = 8192 name = main.main skip = 0, pc = 80579 file = /usr/local/go/src/runtime/proc.go, line = 16 entry = 80336 name = runtime.main skip = 0, pc = 231265 file = /usr/local/go/src/runtime/asm_amd64.s, line = 2232 entry = 231264 name = runtime.goexit
根據測試, 若是是無效 pc (好比0), runtime.Func.FileLine 通常會輸出當前函數的開始行號. 不過在實踐中, 通常會用 runtime.Caller 獲取文件名和行號信息, runtime.Func.FileLine 不多用到(如何獨立獲取pc參數?).
基於前面的幾個函數, 咱們能夠方便的定製一個 CallerName 函數. 函數 CallerName 返回調用者的函數名/文件名/行號等用戶友好的信息.
函數實現以下:
package main import ( "fmt" "runtime" ) func main() { for skip := 0; ; skip++ { name, file, line, ok := CallerName(skip) if !ok { break } fmt.Printf("skip = %v\n", skip) fmt.Printf(" file = %v, line = %d\n", file, line) fmt.Printf(" name = %v\n", name) } } func CallerName(skip int) (name, file string, line int, ok bool) { var pc uintptr if pc, file, line, ok = runtime.Caller(skip + 1); !ok { return } name = runtime.FuncForPC(pc).Name() return }
輸出:
skip = 0 file = /Users/zhangyuchen/go/pro/src/test/main.go, line = 10 name = main.main skip = 1 file = /usr/local/go/src/runtime/proc.go, line = 63 name = runtime.main skip = 2 file = /usr/local/go/src/runtime/asm_amd64.s, line = 2232 name = runtime.goexit
其中在執行 runtime.Caller 調用時, 參數 skip + 1 用於抵消 CallerName 函數自身的調用.
在Go語言中, 除了語言定義的普通函數調用外, 還有閉包函數/init函數/全局變量初始化等不一樣的函數調用類型.
爲了便於測試不一樣類型的函數調用, 咱們包裝一個 PrintCallerName 函數. 該函數用於輸出調用者的信息.
package main import ( "fmt" "runtime" ) var a = PrintCallerName(0, "main.a") var b = PrintCallerName(0, "main.b") func init() { a = PrintCallerName(0, "main.init.a") } func init() { b = PrintCallerName(0, "main.init.b") func() { b = PrintCallerName(0, "main.init.b[1]") }() } func main() { a = PrintCallerName(0, "main.main.a") b = PrintCallerName(0, "main.main.b") func() { b = PrintCallerName(0, "main.main.b[1]") func() { b = PrintCallerName(0, "main.main.b[1][1]") }() b = PrintCallerName(0, "main.main.b[2]") }() } func PrintCallerName(skip int, comment string) bool { name, file, line, ok := CallerName(skip + 1) if !ok { return false } fmt.Printf("skip = %v, comment = %s\n", skip, comment) fmt.Printf(" file = %v, line = %d\n", file, line) fmt.Printf(" name = %v\n", name) return true } func CallerName(skip int) (name, file string, line int, ok bool) { var pc uintptr if pc, file, line, ok = runtime.Caller(skip + 1); !ok { return } name = runtime.FuncForPC(pc).Name() return }
輸出:
skip = 0, comment = main.a file = /Users/zhangyuchen/go/pro/src/test/main.go, line = 8 name = main.init skip = 0, comment = main.b file = /Users/zhangyuchen/go/pro/src/test/main.go, line = 9 name = main.init skip = 0, comment = main.init.a file = /Users/zhangyuchen/go/pro/src/test/main.go, line = 12 name = main.init·1 skip = 0, comment = main.init.b file = /Users/zhangyuchen/go/pro/src/test/main.go, line = 16 name = main.init·2 skip = 0, comment = main.init.b[1] file = /Users/zhangyuchen/go/pro/src/test/main.go, line = 18 name = main.func·001 skip = 0, comment = main.main.a file = /Users/zhangyuchen/go/pro/src/test/main.go, line = 23 name = main.main skip = 0, comment = main.main.b file = /Users/zhangyuchen/go/pro/src/test/main.go, line = 24 name = main.main skip = 0, comment = main.main.b[1] file = /Users/zhangyuchen/go/pro/src/test/main.go, line = 26 name = main.func·003 skip = 0, comment = main.main.b[1][1] file = /Users/zhangyuchen/go/pro/src/test/main.go, line = 28 name = main.func·002 skip = 0, comment = main.main.b[2] file = /Users/zhangyuchen/go/pro/src/test/main.go, line = 30 name = main.func·003
觀察輸出結果, 能夠發現如下幾個規律:
好比如下全局變量的初始化調用者爲 main.init 函數:
var a = PrintCallerName(0, "main.a") var b = PrintCallerName(0, "main.b")
如下兩個 init 函數根據出現順序分別對應 main.init·1 和 main.init·2 :
func init() { // main.init·1 // } func init() { // main.init·2 // }
如下三個閉包根據定義結束順序分別爲 001 / 002 / 003 :
func init() { func(){ // }() // main.func·001 } func main() { func() { func(){ // }() // main.func·002 }() // main.func·003 }
由於, 這些特殊函數調用方式的存在, 咱們須要進一步完善 CallerName 函數.
兩類特殊的調用是 init 類函數調用 和 閉包函數調用.
改進後的 CallerName 函數對 init 類函數調用者統一處理爲 init 函數. 將閉包函數調用這處理爲調用者的函數名.
func CallerName(skip int) (name, file string, line int, ok bool) { var ( reInit = regexp.MustCompile(`init·\d+$`) // main.init·1 reClosure = regexp.MustCompile(`func·\d+$`) // main.func·001 ) for { var pc uintptr if pc, file, line, ok = runtime.Caller(skip + 1); !ok { return } name = runtime.FuncForPC(pc).Name() if reInit.MatchString(name) { name = reInit.ReplaceAllString(name, "init") return } if reClosure.MatchString(name) { skip++ continue } return } return }
輸出:
skip = 0, comment = main.a file = /Users/zhangyuchen/go/pro/src/test/main.go, line = 9 name = main.init skip = 0, comment = main.b file = /Users/zhangyuchen/go/pro/src/test/main.go, line = 10 name = main.init skip = 0, comment = main.init.a file = /Users/zhangyuchen/go/pro/src/test/main.go, line = 13 name = main.init skip = 0, comment = main.init.b file = /Users/zhangyuchen/go/pro/src/test/main.go, line = 17 name = main.init skip = 0, comment = main.init.b[1] file = /Users/zhangyuchen/go/pro/src/test/main.go, line = 20 name = main.init skip = 0, comment = main.main.a file = /Users/zhangyuchen/go/pro/src/test/main.go, line = 24 name = main.main skip = 0, comment = main.main.b file = /Users/zhangyuchen/go/pro/src/test/main.go, line = 25 name = main.main skip = 0, comment = main.main.b[1] file = /Users/zhangyuchen/go/pro/src/test/main.go, line = 32 name = main.main skip = 0, comment = main.main.b[1][1] file = /Users/zhangyuchen/go/pro/src/test/main.go, line = 32 name = main.main skip = 0, comment = main.main.b[2] file = /Users/zhangyuchen/go/pro/src/test/main.go, line = 32 name = main.main
有如下的代碼:
func init() { myInit("1") } func main() { myInit("2") } var myInit = func(name string) { PrintCallerName(0, name+":main.myInit.b") }
輸出:
skip = 0, comment = 1:main.myInit.b file = /Users/zhangyuchen/go/pro/src/test/main.go, line = 10 name = main.init skip = 0, comment = 2:main.myInit.b file = /Users/zhangyuchen/go/pro/src/test/main.go, line = 13 name = main.main
從直觀上看, myInit閉包函數在執行時, 最好輸出 main.myInit 函數名. 可是 main.myInit 只是一個綁定到閉包函數的變量, 而閉包的真正名字是 main.func·???(這裏若是用改進以前的CallerName的話,輸出是main.func·001). 在運行時是沒法獲得 main.myInit 這個名字的.
基於函數調用者信息能夠很容易的驗證各類環境的程序啓動流程.
test:
package main import ( "fmt" "testing" ) func TestPrintCallerName(t *testing.T) { for skip := 0; ; skip++ { name, file, line, ok := CallerName(skip) if !ok { break } fmt.Printf("skip = %v, name = %v, file = %v, line = %v\n", skip, name, file, line) } t.Fail() }
example:
package main import ( myMain "." "fmt" ) func Example() { for skip := 0; ; skip++ { name, file, line, ok := myMain.CallerName(skip) if !ok { break } fmt.Printf("skip = %v, name = %v, file = %v, line = %v\n", skip, name, file, line) } // Output: ? }
運行 go test , 獲得的輸出:
=== RUN TestPrintCallerName skip = 0, name = test.TestPrintCallerName, file = /Users/zhangyuchen/go/pro/src/test/main_test.go, line = 10 skip = 1, name = testing.tRunner, file = /usr/local/go/src/testing/testing.go, line = 447 skip = 2, name = runtime.goexit, file = /usr/local/go/src/runtime/asm_amd64.s, line = 2232 --- FAIL: TestPrintCallerName (0.00s) === RUN: Example --- FAIL: Example (0.00s) got: skip = 0, name = test.Example, file = /Users/zhangyuchen/go/pro/src/test/example_test.go, line = 10 skip = 1, name = testing.runExample, file = /usr/local/go/src/testing/example.go, line = 98 skip = 2, name = testing.RunExamples, file = /usr/local/go/src/testing/example.go, line = 36 skip = 3, name = testing.(*M).Run, file = /usr/local/go/src/testing/testing.go, line = 486 skip = 4, name = main.main, file = test/_test/_testmain.go, line = 54 skip = 5, name = runtime.main, file = /usr/local/go/src/runtime/proc.go, line = 63 skip = 6, name = runtime.goexit, file = /usr/local/go/src/runtime/asm_amd64.s, line = 2232 want: ? FAIL exit status 1 FAIL test 0.008s
分析輸出數據咱們能夠發現, 測試代碼和例子代碼的啓動流程和普通的程序流程都不太同樣.
測試代碼的啓動流程:
例子代碼的啓動流程:
另外, 從這個例子咱們能夠發現, 咱們本身寫的 main.main 函數所在的 main 包也能夠被其餘包導入. 可是其餘包導入以後的 main 包裏的 main 函數就再也不是main.main 函數了. 所以, 程序的入口也就不是本身寫的 main.main 函數了.
var m runtime.MemStats runtime.ReadMemStats(&m) format := "%-40s : %d bytes\n" fmt.Printf(format, "bytes allocated and still in use", m.HeapAlloc) fmt.Printf(format, "bytes obtained from system", m.HeapSys) fmt.Printf(format, "bytes in idle spans", m.HeapIdle) fmt.Printf(format, "bytes in non-idle span", m.HeapInuse) fmt.Printf(format, "bytes released to the OS", m.HeapReleased) fmt.Printf(format, "total number of allocated objects", m.HeapObjects)
輸出:
bytes allocated and still in use : 38928 bytes bytes obtained from system : 851968 bytes bytes in idle spans : 696320 bytes bytes in non-idle span : 155648 bytes bytes released to the OS : 0 bytes total number of allocated objects : 113 bytes
router := httprouter.New() router.HandlerFunc("GET", "/debug/pprof", pprof.Index) router.Handler("GET", "/debug/heap", pprof.Handler("heap")) router.Handler("GET", "/debug/goroutine", pprof.Handler("goroutine")) router.Handler("GET", "/debug/block", pprof.Handler("block")) router.Handler("GET", "/debug/threadcreate", pprof.Handler("threadcreate")) // 啓動時的命令,好比 bin/debug -a=1 router.HandlerFunc("GET", "/debug/pprof/cmdline", pprof.Cmdline) router.HandlerFunc("GET", "/debug/pprof/symbol", pprof.Symbol) router.HandlerFunc("GET", "/debug/pprof/profile", pprof.Profile) router.HandlerFunc("GET", "/debug/pprof/trace", pprof.Trace) http.ListenAndServe(":8080", router)