上卷 程序设计
中卷 标准库
- bufio 1.18
- bytes 1.18
- io 1.18
- container 1.18
- encoding 1.18
- crypto 1.18
- hash 1.18
- index 1.18
- sort 1.18
- context 1.18
- database 1.18
- connection
- query
- queryrow
- exec
- prepare
- transaction
- scan & null
- context
- tcp
- udp
- http
- server
- handler
- client
- h2、tls
- url
- rpc
- exec
- signal
- embed 1.18
- plugin 1.18
- reflect 1.18
- runtime 1.18
- KeepAlived
- ReadMemStats
- SetFinalizer
- Stack
- sync 1.18
- atomic
- mutex
- rwmutex
- waitgroup
- cond
- once
- map
- pool
- copycheck
- nocopy
- unsafe 1.18
- fmt 1.18
- log 1.18
- math 1.18
- time 1.18
- timer
下卷 运行时
源码剖析
附录
文章来源于网络收集而来,版权归原创者所有,如有侵权请及时联系!
4.7.2 系统监控
监控(sysmon)自运行时启动时,就使用专门的线程运行。
runtime.main 里
newm(sysmon, nil)
。
- 如果超过 2 分钟没有垃圾回收,强制执行。
- 网络轮询(netpoll),将结果添加到任务队列。
- 向长时间运行 G 任务发出抢占调度。
- 收回因 syscall 长时间阻塞的 P。
- 执行定时器。
// proc.go // Always runs without a P, so write barriers are not allowed. func sysmon() { idle := 0 // 闲置周期计数。 delay := uint32(0) // 循环间隔。 for { if idle == 0 { // 初始 20 纳秒(us)。 delay = 20 } else if idle > 50 { // 如果闲置 50 个周期,那么间隔加倍。 delay *= 2 } if delay > 10*1000 { // 最高 10 毫秒(ms)。 delay = 10 * 1000 } usleep(delay) // 当前时间,及下个计时器触发时间。 now := nanotime() next, _ := timeSleepUntil() // STW,或没有 P 在工作。 if atomic.Load(&sched.gcwaiting) != 0 || atomic.Load(&sched.npidle) == uint32(gomaxprocs) { // 且没有计时器要触发,短期休眠。 if next > now { atomic.Store(&sched.sysmonwait, 1) notetsleep(&sched.sysmonnote, sleep) now = nanotime() next, _ = timeSleepUntil() atomic.Store(&sched.sysmonwait, 0) noteclear(&sched.sysmonnote) } // 重置计数器。 idle = 0 delay = 20 } lastpoll := int64(atomic.Load64(&sched.lastpoll)) // 如超过 10ms,则轮询网络。 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 if !list.empty() { injectglist(&list) } } // 执行定时器。 // 启动一个 MP,让 schedule 去 checkTimers。 if next < now { // There are timers that should have already run, // perhaps because there is an unpreemptible P. // Try to start an M to run them. startm(nil, false) } // 抢夺阻塞 syscall,抢占调度长时间运行 G。 // 如果成功,那么闲置计数归零,否则累加。 if retake(now) != 0 { idle = 0 } else { idle++ // 也就是说,每个周期都可能累加。 } // 距上次垃圾回收超过 2 分钟,强制执行。 if t := (gcTrigger{kind: gcTriggerTime, now: now}); t.test() && atomic.Load(&forcegc.idle) != 0 { forcegc.idle = 0 var list gList list.push(forcegc.g) injectglist(&list) } } }
retake
在 P 里有专门保存上次检查结果的字段。
schedtick 在 schedule/execute 调用时累加。
syscalltick 在 syscall 等调用时累加。
sysmontick 仅在 sysmon/retak 检查时累加。
// runtime2.go type p struct { schedtick uint32 // incremented on every scheduler call syscalltick uint32 // incremented on every system call sysmontick sysmontick // last tick observed by sysmon }
// proc.go type sysmontick struct { schedtick uint32 schedwhen int64 syscalltick uint32 syscallwhen int64 }
对比调用和检查计数(tick),就可知道是否执行了新任务。再加上 sysmon 检查间隔时间(when),可判断是否超时。
// proc.go func retake(now int64) uint32 { n := 0 // 循环检查所有 P。 for i := 0; i < len(allp); i++ { _p_ := allp[i] if _p_ == nil { continue } // 获取检查计数。 pd := &_p_.sysmontick s := _p_.status sysretake := false if s == _Prunning || s == _Psyscall { // 计数不同,表示检查间隔期间执行了新任务,跳过。 t := int64(_p_.schedtick) if int64(pd.schedtick) != t { // 更新检查计数和时间。 pd.schedtick = uint32(t) pd.schedwhen = now } else if pd.schedwhen+forcePreemptNS <= now { // 计数相同,表示还在执行上次任务。 // 距上次检查时间超过 10ms,发出抢占调度。 preemptone(_p_) sysretake = true } } if s == _Psyscall { // 计数不同,执行了新任务,跳过。 t := int64(_p_.syscalltick) if !sysretake && int64(pd.syscalltick) != t { // 更新检查计数和时间。 pd.syscalltick = uint32(t) pd.syscallwhen = now continue } // 如当前 syscall P 没额外任务,尚有自旋或闲置 MP,且检查间隔不超过 10ms,跳过。 // 反正有自旋 MP 去执行新任务,没必要强行剥夺 syscall P。当然,时间过长肯定不行。 if runqempty(_p_) && atomic.Load(&sched.nmspinning) + atomic.Load(&sched.npidle) > 0 && pd.syscallwhen+10*1000*1000 > now { continue } // 抢回 P。 if atomic.Cas(&_p_.status, s, _Pidle) { _p_.syscalltick++ handoffp(_p_) } } } }
// proc.go // forcePreemptNS is the time slice given to a G before it is preempted. const forcePreemptNS = 10 * 1000 * 1000 // 10ms
// proc.go // Tell the goroutine running on processor P to stop. func preemptone(_p_ *p) bool { mp := _p_.m.ptr() gp := mp.curg // 设定抢占标记。 gp.preempt = true gp.stackguard0 = stackPreempt // 请求异步抢占。 if preemptMSupported && debug.asyncpreemptoff == 0 { _p_.preempt = true preemptM(mp) } return true }
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论