Go语言基础之runtime包
文章引用自
Golang中runtime的使用
runtime调度器是非常有用的东西,关于runtime包几个方法:
-
Gosched:让当前线程让出cpu以让其他线程运行,它不会挂起当前线程,因此当前线程未来会继续执行
-
NumCPU:返回当前系统的CPU核数量
-
GOMAXPROCS:设置最大的可同时使用的CPU核数
-
Goexit:退出当前goroutine(但是defer语句会照常执行)
-
NumGoroutine:返回真该执行和排队的任务总数
-
GOOS:目标操作系统
- GOROOT:返回本机的GO路径
package main import "fmt" import "runtime" func main() { fmt.Println("cpus:", runtime.NumCPU()) // 返回当前系统的CPU核数量 fmt.Println("goroot:", runtime.GOROOT()) // fmt.Println("NumGoroutine:", runtime.NumGoroutine()) // 返回真该执行和排队的任务总数 fmt.Println("archive:", runtime.GOOS) // 目标操作系统 } 运行结果 cpus: 12 goroot: /usr/local/go NumGoroutine: 1 archive: darwin
GOMAXPROCS
// 修改最大可同时使用CPU核数
Golang默认所有任务都运行在一个cpu核里,如果要在goroutine中使用多核,可以使用runtime.GOMAXPROCS函数修改,当参数小于1时使用默认值。
package main import "fmt" import "runtime" import "time" func init(){ runtime.GOMAXPROCS(4) // 修改最大可同时使用CPU核数 } func main(){ t := time.Now().Nanosecond() for i:=0;i<100000;i++{ fmt.Println(i*i*i*i) } t2 := time.Now().Nanosecond() fmt.Println(t2-t) }
Gosched
// 让当前线程让出cpu以让其他线程运行,它不会挂起当前线程,因此当前线程未来会继续执行
这个函数的作用是让当前goroutine让出CPU,当一个goroutine发生阻塞,Go会自动地把与该goroutine出于同一系统线程的其他goroutine转移到另一个系统线程上去,使得这些goroutine不阻塞。
package main import ( "fmt" "runtime" ) func init() { runtime.GOMAXPROCS(1) //使用单核 } func main() { exit := make(chan int) go func() { defer close(exit) go func() { fmt.Println("b") }() }() for i := 0; i < 4; i++ { fmt.Println("a:", i) if i == 1 { runtime.Gosched() //切换任务 } } <-exit }
在windows系统上,结果为:
a: 0 a: 1 a: 2 a: 3
切换成多核,每次运行的结果都不一样:
package main import ( "fmt" "runtime" ) func main() { runtime.GOMAXPROCS(4) exit := make(chan int) go func() { defer close(exit) go func() { fmt.Println("b") }() }() for i := 0; i < 10; i++ { fmt.Println("a:", i) if i == 4 { runtime.Gosched() //切换任务 } } <-exit }
总结:多核比较适合那种CPU密集型程序,如果是IO密集型使用多核会增加CPU切换的成本。
GOMAXPROCS和sync配合使用
sync.WaitGroup只有三个方法,Add(),Done()和Wait()。其中Done()是Add(-1)的别名。简单来说,使用Add()添加计数,Done()减少一个计数,计数不为0,阻塞Wait()的运行。
package main import ( "runtime" "sync" "fmt" ) func main(){ runtime.GOMAXPROCS(2) var wg sync.WaitGroup wg.Add(2) fmt.Printf("Starting go routines") go func(){ defer wg.Done() for char := 'a';char<'a'+26;char++{ fmt.Printf("%c",char) } }() go func() { defer wg.Done() for number := 1;number<27;number++{ fmt.Printf("%d",number) } }() fmt.Println("\nWaiting to finish") wg.Wait() fmt.Println("\n terminating program") }
运行结果:
Starting go routines Waiting to finish 1234567891011121314151617181920212223242526abcdefghijklmnopqrstuvwxyz terminating program
总结:首先是wg.Add(2)计数为2,阻塞wg.Wait()的运行,然后是wg.Done()减少计数到0,放开wg.Wait()的运行。
runtime.Caller
Caller 方法反应的是堆栈信息中某堆栈帧所在文件的绝对路径和语句所在文件的行数。而 skip 表示的是从下往上数第几个堆栈帧。如果要打印全部堆栈信息可以直接使用 debug.PrintStack()
来实现。
func Caller(skip int) (pc uintptr, file string, line int, ok bool)
参数:skip是要提升的堆栈帧数,0-当前函数,1-上一层函数,....
返回值:
pc是uintptr这个返回的是函数指针
file是函数所在文件名目录
line所在行号
ok 是否可以获取到信息
示例:
我们分别打印skip为0-3的相关信息
package main import ( "fmt" "runtime" ) func main() { for i := 0 ; i< 4; i++ { test(i) } } func test(skip int) { call(skip) } func call(skip int) { pc,file,line,ok := runtime.Caller(skip) pcName := runtime.FuncForPC(pc).Name() //获取函数名 fmt.Println(fmt.Sprintf("%v %s %d %t %s",pc,file,line,ok,pcName))
返回
17412399 /Users/songzhibin/go/src/Songzhibin/study/3.go 19 true main.call 17412303 /Users/songzhibin/go/src/Songzhibin/study/3.go 15 true main.test 17412294 /Users/songzhibin/go/src/Songzhibin/study/3.go 10 true main.main 16953069 /usr/local/go/src/runtime/proc.go 203 true runtime.main