最近在看协程的实现,想弄明白go的调度器是不是抢占式,如果某个goroutine运行的时间太久,并且没有主动让出cpu,go调度器是否会切换到其他goroutine运行。
看了一些其他的文章,总结了一下,go的调度器勉强算的上是抢占式的,但是不是在所有情况下都能抢占。按照go调度器的设计,最终还是让长时间运行的goroutine自己放弃cpu的,以主动放弃达到抢占的目的。go调度器会起一个守护线程去监控各个协程的运行时间,一旦某个协程运行超时,就给这个协程的栈空间设置一个超时的标志位,协程在运行的过程中经常检查这个标志位,检测自己是否运行时间超时。一旦检测到超时,就主动调用yield将cpu的执行权交给调度器,让调度器选择下一个goroutine执行。
那关键是协程在什么时机检查这个超时标志位呢?那就是goroutine每次在执行非内联函数的时候。因此如果goroutine空循环,或者没有执行非内联函数,那么还是无法被抢占的。会永远运行下去,调度器也无能为力。
go调度器对计算密集型的任务,的是不是真的跟以上说法一致?
下面的代码,编译好后运行在只有单cpu的机子上运行(必须是单核的,如果是多核,go调度器会把两个协程分别运行在多个线程上,让我们无法分别是否切换了协程执行),可以在虚拟机上运行,虚拟机方便更改cpu核数
package main
import (
"fmt"
)
func thread(i int){
for {
if 0 == i {
fmt.Printf("long printf ================================%v\n",i)
} else {
fmt.Printf("short printf%v\n",i)
}
}
}
func main(){
for i := 0; i < 2; i++ {
go thread(i)
}
//runtime.Gosched()
fmt.Scanln()
}
以上测试结果,long printf和short printf 都会打印出来,只是需要观察久一点,大概几分钟,从我自己测试的结果看,协程切换的不频繁。说明在只使用printf函数的情况下,协程还是会得到切换的,我们不清楚是不是printf函数内部是不是有出让cup的动作,或者什么其他机制检测运行超时,但是的确是有协程的切换的
参考文章: