golang中goroutine的执行问题记录
先上代码
func main() {
runtime.GOMAXPROCS(1)
number := make(chan int, 10)
wg := sync.WaitGroup{}
wg.Add(10)
for i := 0; i < 10; i++ {
number <- i
}
go func() {
for i := range number {
fmt.Println("i:===>", i)
wg.Done()
}
}()
wg.Wait()
}
运行结果如下
i:===> 0
i:===> 1
i:===> 2
i:===> 3
i:===> 4
i:===> 5
i:===> 6
i:===> 7
i:===> 8
i:===> 9
我们知道,以上这种写法是比较规范的对goroutine和channel的使用,由于是顺序传入channel中,所以另一个goroutine读取number时也是顺序的。由此并不能分析出goroutine的执行顺序。
接下来,上代码
func main() {
runtime.GOMAXPROCS(1)
number := make(chan int, 10)
wg := sync.WaitGroup{}
wg.Add(10)
for i := 0; i < 10; i++ {
number <- i
}
go func() {
for i := range number {
fmt.Println("i:===>", i)
wg.Done()
}
}()
go func() {
for i := range number {
fmt.Println("i:+++>", i)
wg.Done()
}
}()
wg.Wait()
}
运行结果如下
i:+++> 0
i:+++> 1
i:+++> 2
i:+++> 3
i:+++> 4
i:+++> 5
i:+++> 6
i:+++> 7
i:+++> 8
i:+++> 9
这里相对于代码段1来说,只是增加了另外一个goroutine,执行同样的读取操作。可是执行结果大相径庭。原因就在于goroutine的启用与执行,以及CPU的调度。CPU调度下面再说,先看看goroutine的创建与执行。
goroutine的启用与执行
我们都知道,一个程序必须有一个入口main函数,golang也是如此。但在golang中由于引入了goroutine的概念,所以入口main函数可以看作一个goroutine,通常称为主goroutine。
再来看下面的例子
func main() {
for i:=0; i<10; i++ {
go func() {
fmt.Println("i:", i)
}()
}
}
这段代码执行起来绝大部分情况下是不会有任何输出的,当然也不能排除有输出。
原因都知道,因为主routine执行完成for循环就结束了,而对于其他goroutine不论正在执行还是尚未执行都将关闭。
而goroutine之间是公平的竞争关系。
实际上以上代码如果让主routine阻塞等到其他goroutine结束后推出,也是有些问题存在。如下
func main() {
runtime.GOMAXPROCS(1)
for i := 0; i < 10; i++ {
go func() {
fmt.Println("i:", i)
}()
}
time.Sleep(5 * time.Second)
}
运行结果
i: 10
i: 10
i: 10
i: 10
i: 10
i: 10
i: 10
i: 10
i: 10
i: 10
如果启用golang运行时CPU为单核,则会出现上述结果。我想你应该是想连续输出0-9吧。
原因很明显,虽然运行时单核执行,保证了执行顺序。所以调度没问题。
问题就在匿名函数i的数值,为了保证输出可以使用channel来在goroutine中传递数值。或者直接将变量i传入匿名函数。
如下两种
func main() {
runtime.GOMAXPROCS(10)
for i := 0; i < 10; i++ {
go func(i int) {
fmt.Println("i:", i)
}(i)
}
time.Sleep(5 * time.Second)
}
func main() {
runtime.GOMAXPROCS(1)
num := make(chan int, 10)
for i := 0; i < 10; i++ {
num <- i
go func() {
fmt.Println("i:", <-num)
}()
}
time.Sleep(5 * time.Second)
}
但是细心的你会发现第一种并不是顺序输出0-9,第二种就完美的输出了0-9。原因在哪里呢?
如下第一种输出:
i: 9
i: 0
i: 1
i: 2
i: 3
i: 4
i: 5
i: 6
i: 7
i: 8
这就涉及到goroutine的启用与调度了。由于goroutine之间是公平竞争关系,所以并不能保证goroutine的执行顺序 。
个人见解,不一定对,以下未查证,欢迎评论区交流。
goroutine的创建并不是真正的创建,我们称为启用。当执行到一个go语句后,golang运行时系统会试图从某个空闲的goroutine队列中获取,如果当前没有空闲的goroutine时才会创建一个新的goroutine。
知道了这,就能解释为什么9出现在第一个位置,而其他则是顺序输出了。
以上都是runtime.GOMAXPROCS(1)运行下
也就是单核运行的结果
如果没有单核的限制,则会出现各种结果,暂不讨论。