尽管 Go 编译器产生的是本地可执行代码,这些代码仍旧运行在 Go 的 runtime(这部分的代码可以在 runtime 包中找到)当中。这个 runtime 类似 Java 和 .NET 语言所用到的虚拟机,它负责管理包括内存分配、垃圾回收、栈处理、goroutine、channel、切片(slice)、map 和反射(reflection)等等。

最近在写一个http server里涉及到for循环时,在查找解决方案时,从网上查看到的结果有提到runtime.Gosched (该问题请查看stackoverflow)。runtime下的函数和方法比较多,而本篇只介绍runtime下的三个函数:Gosched、Goexit、GOMAXPROCS。

一、runtime调度三函数的介绍

runtime.Gosched(),用于让出CPU时间片,让出当前goroutine的执行权限,调度器安排其它等待的任务运行,并在下次某个时候从该位置恢复执行。这就像跑接力赛,A跑了一会碰到代码runtime.Gosched()就把接力棒交给B了,A歇着了,B继续跑。

runtime.Goexit(),调用此函数会立即使当前的goroutine的运行终止(终止协程),而其它的goroutine并不会受此影响。runtime.Goexit在终止当前goroutine前会先执行此goroutine的还未执行的defer语句。请注意千万别在主函数调用runtime.Goexit,因为会引发panic。

runtime.GOMAXPROCS(),用来设置可以并行计算的CPU核数最大值,并返回之前的值。默认此函数的值与 CPU 逻辑个数相同,即有多少个goroutine并发执行,当然可以设置它,它的取值是1~256。最好在主函数在开始前设置它,因为设置它会停止当前程序的运行。

注意:GO默认是使用一个CPU核的,除非设置runtime.GOMAXPROCS 。在多核环境下,什么情况下设置runtime.GOMAXPROCS会比较好的提高速度呢?适合于CPU密集型、并行度比较高的情景。如果是IO密集型,CPU之间的切换也会带来性能的损失。

二、Gosched()

先看下面一个没有使用Gosched函数的示例代码,如下:

 1package main
 2import (
 3    "fmt"
 4)
 5func main() {
 6    go func() { //子协程   //没来的及执行主进程结束
 7        for i := 0; i < 5; i++ {
 8            fmt.Println("go")
 9        }
10    }()
11    for i := 0; i < 2; i++ { //默认先执行主进程主进程执行完毕
12        fmt.Println("hello")
13    }
14}

该代码运行后的结果如下:

1[root@361way runtime]# go run 01-sched.go
2hello
3hello

再看一个使用了Gosched函数的结果:

 1package main
 2import (
 3    "fmt"
 4    "runtime"
 5)
 6func main() {
 7    go func() {  //让子协程先执行
 8        for i := 0; i < 5; i++ {
 9            fmt.Println("go")
10        }
11    }()
12    for i := 0; i < 2; i++ {
13        //让出时间片,先让别的协议执行,它执行完,再回来执行此协程
14        runtime.Gosched()
15        fmt.Println("hello")
16    }
17}

代码运行结果如下:

1[root@361way runtime]# go run 02-sched.go
2go
3go
4go
5go
6go
7hello
8hello

三、Goexit()

示例代码如下:

 1package main
 2import (
 3    "fmt"
 4    "runtime"
 5)
 6func test() {
 7    defer fmt.Println("ccccccccccccc")
 8    //return //终止此函数
 9    runtime.Goexit() //终止所在的协程
10    fmt.Println("dddddddddddddddddddddd")
11}
12func main() {
13    //创建新建的协程
14    go func() {
15        fmt.Println("aaaaaaaaaaaaaaaaaa")
16        //调用了别的函数
17        test()
18        fmt.Println("bbbbbbbbbbbbbbbbbbb")
19    }() //别忘了()
20    //特地写一个死循环,目的不让主协程结束
21    for {
22    }
23}

调用结果如下:

1[root@361way runtime]# go run 03-goexit.go
2aaaaaaaaaaaaaaaaaa
3ccccccccccccc
4^Csignal: interrup

可以看出只调用了aaa和ccc相关的行。后面的ddd和bbb相关的行由于所在的协程被终止了,所以后面的结果无法执行。

四、GOMAXPROCS()

 1package main
 2import (
 3    "fmt"
 4    "runtime"
 5)
 6func init() {
 7    runtime.GOMAXPROCS(1)  //使用单核
 8}
 9func main() {
10    exit := make(chan int)
11    go func() {
12        defer close(exit)
13        go func() {
14            fmt.Println("b")
15        }()
16    }()
17    for i := 0; i < 4; i++ {
18        fmt.Println("a:", i)
19        if i == 1 {
20            runtime.Gosched()  //切换任务
21        }
22    }
23    <-exit
24}

上面的代码测试结果如下:

1[root@361way runtime]# go run maxprocs.go
2a: 0
3a: 1
4b
5a: 2
6a: 3

将runtime.GOMAXPROCS(1) 修改为runtime.GOMAXPROCS(4)再测试时,结果如下:

1[root@361way runtime]# go run maxprocs.go
2a: 0
3a: 1
4a: 2
5a: 3

注意:上面的CPU核数修改时,要保证测试主机自身的核数多于4,不然还是会输出上面带b的结果。