Go runtime 调度器中系统调用引发的抢占机制是如何的?
摘要:原创文章,欢迎转载,转载请注明出处,谢谢。 0. 前言 第八讲介绍了当 goroutine 运行时间过长会被抢占的情况。这一讲继续看 goroutine 执行系统调用时间过长的抢占。 1. 系统调用时间过长的抢占 看下面的示例: func
原创文章,欢迎转载,转载请注明出处,谢谢。
0. 前言
第八讲介绍了当 goroutine 运行时间过长会被抢占的情况。这一讲继续看 goroutine 执行系统调用时间过长的抢占。
1. 系统调用时间过长的抢占
看下面的示例:
func longSyscall() {
timeout := syscall.NsecToTimeval(int64(5 * time.Second))
fds := make([]syscall.FdSet, 1)
if _, err := syscall.Select(0, &fds[0], nil, nil, &timeout); err != nil {
fmt.Println("Error:", err)
}
fmt.Println("Select returned after timeout")
}
func main() {
threads := runtime.GOMAXPROCS(0)
for i := 0; i < threads; i++ {
go longSyscall()
}
time.Sleep(8 * time.Second)
}
longSyscall goroutine 执行一个 5s 的系统调用,在系统调用过程中,sysmon 会监控 longSyscall,发现执行系统调用过长,会对其抢占。
回到 sysmon 线程看它是怎么抢占系统调用时间过长的 goroutine 的。
func sysmon() {
...
idle := 0 // how many cycles in succession we had not wokeup somebody
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)
...
// retake P's blocked in syscalls
// and preempt long running G's
if retake(now) != 0 {
idle = 0
} else {
idle++
}
...
}
}
类似于运行时间过长的 goroutine,调用 retake 进行抢占:
func retake(now int64) uint32 {
n := 0
lock(&allpLock)
for i := 0; i < len(allp); i++ {
pp := allp[i]
if pp == nil {
continue
}
pd := &pp.sysmontick
s := pp.status
sysretake := false
if s == _Prunning || s == _Psyscall { // goroutine 处于 _Prunning 或 _Psyscall 时会抢占
// Preempt G if it's running for too long.
t := int64(pp.schedtick)
if int64(pd.schedtick) != t {
pd.schedtick = uint32(t)
pd.schedwhen = now
} else if pd.schedwhen+forcePreemptNS <= now {
// 对于 _Prunning 或者 _Psyscall 运行时间过长的情况,都会进入 preemptone
// preemptone 我们在运行时间过长的抢占中介绍过,它主要设置了 goroutine 的标志位
// 对于处于系统调用的 goroutine,这么设置并不会抢占。
