为什么需要goroutine抢占调度?
通过本文我们已经知道,go会在每次调度goroutine的时候检查定时器是否就绪了,所以如果仅靠goroutine主动让出(系统调用阻塞、读写chan阻塞…)而不对运行时间过长的goroutine进行抢占的话,这些定时器的调度时间可能会与预期有很大的偏差。
goroutine抢占调度怎么实现的呢?
怎么发现某些goroutine需要被抢占?
通过上面这篇文章我们知道在go程序启动时会调用runtime.main
,而它会创建一个监控线程,这个监控线程很特殊,不会关联任何p,只执行监控函数sysmon
,我们来看看它的逻辑。
func sysmon() {
......
delay := uint32(0)
for {
if idle == 0 {
// start with 20us sleep...
delay = 20
} else if idle > 50 {
// start doubling the sleep after 1ms...
delay *= 2
}
if delay > 10*1000 {
// up to 10ms
delay = 10 * 1000
}
usleep(delay) //每隔delay时间就会开启执行一次监控任务
......
// poll network if not polled for more than 10ms
// 每10ms定时检测epoll事件就绪的g
lastpoll := int64(atomic.Load64(&sched.lastpoll))
if netpollinited() && lastpoll != 0 && lastpoll+10*1000*1000 < now {
atomic.Cas64(&sched.lastpoll, uint64(lastpoll), uint64(now))
list := netpoll(0) // non-blocking - returns list of goroutines
......
}
......
// retake P's blocked in syscalls
// and preempt long running G's
// 抢占长时间运行的g+处于系统调用的p
if retake(now) != 0 {
idle = 0
} else {
idle++
}
......
}
}
再来看看retake
是怎么实现抢占的。
func retake(</