看一段代码,请问输出什么?
package main
import "time"
func main() {
var testNum = 0
go func() {
time.Sleep(10000)
testNum = 1
}()
for testNum == 0 {
if testNum==1 {
println("testNum=1")
}
println("testNum=",testNum)
}
println("loop end.")
}
输出结果:
从结果中,能发现2个问题:
1:循环确实结束了,说明testNum!=0 2:timeNum!=0,则说明TetsNum = 1赋值成功了,但是里面的=1判断却没有打印,为什么呢?
这是因为 main协程和 子协程共享变量造成的问题,主要执行流程如下:
package main
import "time"
func main() {
var testNum = 0
go func() {
time.Sleep(10000) //隔了10000纳秒
testNum = 1 //赋值为1
}()
for testNum == 0 {
//在10000纳秒之前,这个值都为0
if testNum==1 { //在执行完这次判断后,testNum才更新为1,此时已经没有条件进入此循环,所以此代码永远不执行
println("testNum=1")
}
println("testNum=",testNum)
//10000纳秒之前,为0
//在执行完这条后,testNum突然被更新为了1,所以不会进行下一次循环
}
println("loop end.")
}
那么,这里面又涉及到了一个新的问题:
为什么是刚好在执行完一次循环之后,才刚好轮到testNum=1,而不是在执行前之前轮转到呢?
这就涉及到了go的协程调度问题了,具体是怎么调度的呢?
go的协程调度为 [典藏版] Golang 调度器 GMP 原理与调度全分析
简单说明:
G:协程
M:运行的线程
P:执行线程的处理器,可以理解为cpu中的线程/进程
- 在运行时,G必须绑定在M上,M必须在P上才可以运行程序,而cpu调度器执行的是P,也就是有多少核心,或者有多少个P,就可以同时运行多少个M/G
- 多个G绑定在M上,在发生syscall或者io阻塞时,会自动挂起,M将切换其他G执行,当G运行时间超过10ms(1.14后加入),会自动切换成其他协程
理解这2句话就够了,我们回到代码:
因为加了输出,导致了协程一定会切换,所以100%可以复现上面的问题,如果这句输出放到上面去运行,则变成了100%输出 testNum=1:
刚刚我们看到了GMP的第一点,有多少个P,就应该有多少个M/G同时运行,那么问题来了,为什么上面的2个协程没有并行呢?
GMP的另一个特点:
- P可能会有多个M绑定,当M阻塞后,P将绑定其他M进行执行
- M会有多个G绑定,当一个G阻塞后,将获取一个新的G进行运行 - 如果M的G已经超出数量后,将会分一半给其他的M
- 如果M没有可以执行的G后,将会偷其他M的G
在示例代码,由于是先执行的go func,sleep(协程2),所以M在执行main开始之后,立即开始执行协程2,同时由于协程2 sleep阻塞,所以切回main协程运行,在刚执行的时候,由于并没有繁忙情况,所以没有启用M2和P2进行运行,所以没有实现并行
本文为仙士可原创文章,转载无需和我联系,但请注明来自仙士可博客www.php20.cn