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,这么设置并不会抢占。
阅读全文