5131
5132
5133
5134
5135
5136
5137
5138
5139
5140
5141
5142
5143
5144
5145
5146
5147
5148
5149
5150
5151
5152
5153
5154
5155
5156
5157
5158
5159
5160
5161
5162
5163
5164
5165
5166
5167
5168
5169
5170
5171
5172
5173
5174
5175
5176
5177
5178
5179
5180
5181
5182
5183
5184
5185
5186
5187
5188
5189
5190
5191
5192
5193
5194
5195
5196
5197
5198
5199
5200
5201
5202
5203
5204
5205
5206
5207
5208
5209
5210
5211
5212
5213
5214
5215
5216
5217
5218
5219
5220
5221
5222
5223
5224
5225
5226
5227
5228
5229
5230
5231
5232
5233
5234
5235
5236
5237
5238
5239
5240
5241
5242
5243
5244
5245
5246
5247
5248
5249
5250
5251
5252
5253
5254
5255
5256
5257
5258
5259
5260
5261
5262
5263
5264
5265
5266
5267
5268
5269
5270
5271
5272
5273
5274
5275
5276
5277
5278
5279
5280
5281
5282
5283
5284
5285
5286
5287
5288
5289
5290
5291
5292
5293
5294
5295
5296
5297
5298
5299
5300
5301
5302
5303
5304
5305
5306
5307
5308
5309
5310
5311
5312
5313
5314
5315
5316
5317
5318
5319
5320
5321
5322
5323
5324
5325
5326
5327
5328
5329
5330
5331
5332
5333
5334
5335
5336
5337
5338
5339
5340
5341
5342
5343
5344
5345
5346
5347
5348
5349
5350
5351
5352
5353
5354
5355
5356
5357
5358
5359
5360
5361
5362
5363
5364
5365
5366
5367
5368
5369
5370
5371
5372
5373
5374
5375
5376
5377
5378
5379
|
// Always runs without a P, so write barriers are not allowed.
//
//go:nowritebarrierrec
func sysmon() {
lock(&sched.lock)
sched.nmsys++ // 系统M数量
checkdead() // 检查死锁
unlock(&sched.lock)
lasttrace := int64(0) // 最后跟踪时间
// idle:在以下两种情况下才会重置为0
// 1. 有陷入系统调度的P需要被抢占时。
// 2. sysmon从深度睡眠中醒来时。
// 进入深度睡眠在这两种条件下:
// 1. STW期间,sysmon可以进入深度睡眠。
// 2. 在所有P都空闲时(可能都陷入系统调用)。
// 其他情况idle会累加,因此sysmon的调度间隔会趋向于10ms。这一个goroutine运行的时间片时间值。
idle := 0 // how many cycles in succession we had not wokeup somebody
// 下次sysmon运行的时间间隔,微秒。根据idle计算而来。
delay := uint32(0)
for {
// 1) 计算下次运行时间间隔
// 1. 默认20us。(20微秒)。
// 2. 连续50个周期无事可做则翻倍时间,后面依次翻倍。
// 3. 最高10ms。(10毫秒)。
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
}
// 系统调用sleep delay微秒
usleep(delay)
// sysmon should not enter deep sleep if schedtrace is enabled so that
// it can print that information at the right time.
//
// It should also not enter deep sleep if there are any active P's so
// that it can retake P's from syscalls, preempt long running G's, and
// poll the network if all P's are busy for long stretches.
//
// It should wakeup from deep sleep if any P's become active either due
// to exiting a syscall or waking up due to a timer expiring so that it
// can resume performing those duties. If it wakes from a syscall it
// resets idle and delay as a bet that since it had retaken a P from a
// syscall before, it may need to do it again shortly after the
// application starts work again. It does not reset idle when waking
// from a timer to avoid adding system load to applications that spend
// most of their time sleeping.
//
// 如果启用了 schedtrace,sysmon 不应进入深度睡眠,以便它可以在正确的时间打印该信息
// 如果有任何活动的P,它也不应该进入深度睡眠,以便它可以从系统调用中重新获取P,
// 抢占长时间运行的G,并在所有P长时间忙碌时轮询网络
// 如果任何 P 由于退出系统调用或由于计时器到期而唤醒,
// 它应该从深度睡眠中唤醒,以便它可以恢复执行这些职责
// 果它从系统调用中唤醒,它会重置空闲和延迟作为赌注,因为它之前已经从系统调用中重新获得了P,
// 它可能需要在应用程序再次开始工作后不久再次这样做
// 它不会在从计时器唤醒时重置空闲,以避免将系统负载添加到大部分时间都在休眠的应用程序
now := nanotime() // 当前时间
// 2) 满足以下条件工作线程会进入深度睡眠:
// 1. STW正在等待其他P停下来,这段时间sysmon线程可以深度睡眠,在start the world时会唤醒sysmon。
// 2. 全部P都处于空闲,这段时间sysmon线程可以深度睡眠,这可能是处于系统调用中时,系统调用返回时会唤醒sysmon。
if debug.schedtrace <= 0 && (sched.gcwaiting != 0 || atomic.Load(&sched.npidle) == uint32(gomaxprocs)) {
lock(&sched.lock) // mutex lock
// 加锁后,再次判断一次原因是获取锁这段时间可能条件不成立了。
if atomic.Load(&sched.gcwaiting) != 0 || atomic.Load(&sched.npidle) == uint32(gomaxprocs) {
syscallWake := false // 系统调用唤醒?
// 2.1) 最近一次timer的触发时间点或没有timer时都应该sleep。
// timeSleepUntil函数只会在sysmon和checkdead函数中被调用:
// 1. next表示最先触发timer的时间点,timeSleepUntil函数会遍历所有的P取选择最小的timer触发时间点。
// 2. 返回 maxWhen = 1<<63 - 1,表示没有定时器。
next, _ := timeSleepUntil()
// 2.2) 还未到触发timer的时间点时或没有timer,这段时间可以sleep。
if next > now {
atomic.Store(&sched.sysmonwait, 1) // sched.sysmonwait = 1
unlock(&sched.lock)
// Make wake-up period small enough
// for the sampling to be correct.
//
// 使唤醒周期足够小,以保证取样正确。
// 2.3) 计算睡眠时间间隔最大值1分钟。
sleep := forcegcperiod / 2 // 1min
if next-now < sleep {
sleep = next - now
}
// osRelaxMinNS 表示如果下一个计时器从现在开始少于 60 毫秒,则 sysmon 不应该 osRelax
// 由于 osRelaxing 可能会将计时器分辨率降低到 15.6 毫秒,这将计时器错误保持在大约 4 分之一以下
// const osRelaxMinNS = 0
shouldRelax := sleep >= osRelaxMinNS
if shouldRelax {
// osRelax 在与所有空闲的 P 之间转换时由调度程序调用
// 在 linux amd64 下该函数为空
osRelax(true)
}
// 2.4) 在sched.sysmonnote上睡眠sleep纳秒。
// 睡眠 sleep ns 时间,睡眠在 sched.sysmonnote 上。
// 最长情况会睡眠1min,也就是全部P都无事可做时。
// sleep时间后,也就是最新的timer需要触发的时间点,唤醒监控线程。
// 当 STW 正在进行时,这里会把监控线程sleep 1min,在start the world时会唤醒
// 在sched.sysmonnote上的监控线程。
// 当系统调用返回,在runtime_exitsyscall()函数中会响应的唤醒
// 在sched.sysmonnote上的监控线程。
// syscallWake = true。
syscallWake = notetsleep(&sched.sysmonnote, sleep)
if shouldRelax {
osRelax(false)
}
lock(&sched.lock)
// sched.sysmonwait = 0;sched.sysmonnote.key = 0;
atomic.Store(&sched.sysmonwait, 0)
noteclear(&sched.sysmonnote)
}
// 由系统调用醒来或触发timer时间点已经过了,重置计时。
if syscallWake {
idle = 0
delay = 20
}
}
unlock(&sched.lock)
}
lock(&sched.sysmonlock)
// Update now in case we blocked on sysmonnote or spent a long time
// blocked on schedlock or sysmonlock above.
//
// 如果我们在sysmonnote上被阻塞,或者在上面的schedlock或sysmonlock上花了很长时间阻塞,现在更新。
now = nanotime()
// trigger libc interceptors if needed
if *cgo_yield != nil {
asmcgocall(*cgo_yield, nil)
}
// 3) network poll; 网络轮询。
// 网络轮询的时间间隔设置为10ms。
// poll network if not polled for more than 10ms
//
// 超过10ms没有进行网络轮询,则进行网络轮询。
// sched.lastpoll:记录的是上次执行netpoll的时间。
// 1. 如果等于0,则表示某个线程正在阻塞式地执行netpoll。
// 2. 大于0,则是上次执行时间点。
lastpoll := int64(atomic.Load64(&sched.lastpoll))
// 以下三种情况不会轮询网络:
// 1. 没有初始化 netpoll 时。
// 2. 其他线程阻塞式访问 netpoll 时。
// 3. 上次轮询时间还没到 10ms 时。
// 需要查看network:已初始化 && 没有其他线程在阻塞调用epoll && 上次epoll已超过10ms了
if netpollinited() && lastpoll != 0 && lastpoll+10*1000*1000 < now {
// CAS更新 sched.lastpoll 时间。
atomic.Cas64(&sched.lastpoll, uint64(lastpoll), uint64(now))
// 3.1) 轮询 poll。参数0立即返回。
// network poll是否有就绪的事件。
// 传递参数0表示epoll轮询wait等待函数立即返回。
// 非阻塞式轮询,返回就绪的goroutine列表。
list := netpoll(0) // non-blocking - returns list of goroutines
// 存在就绪的 goroutine。
if !list.empty() {
// Need to decrement number of idle locked M's
// (pretending that one more is running) before injectglist.
// Otherwise it can lead to the following situation:
// injectglist grabs all P's but before it starts M's to run the P's,
// another M returns from syscall, finishes running its G,
// observes that there is no work to do and no other running M's
// and reports deadlock.
//
// 设置 sched.nmidlelocked += -1
incidlelocked(-1)
// 处理准备好的goroutine
// 该函数在调度循环函数中有详细注解。
injectglist(&list)
incidlelocked(1)
}
}
// 在 linux amd64 下不会触发。
if GOOS == "netbsd" && needSysmonWorkaround {
// netpoll is responsible for waiting for timer
// expiration, so we typically don't have to worry
// about starting an M to service timers. (Note that
// sleep for timeSleepUntil above simply ensures sysmon
// starts running again when that timer expiration may
// cause Go code to run again).
//
// However, netbsd has a kernel bug that sometimes
// misses netpollBreak wake-ups, which can lead to
// unbounded delays servicing timers. If we detect this
// overrun, then startm to get something to handle the
// timer.
//
// See issue 42515 and
// https://gnats.netbsd.org/cgi-bin/query-pr-single.pl?number=50094.
if next, _ := timeSleepUntil(); next < now {
startm(nil, false)
}
}
if atomic.Load(&scavenge.sysmonWake) != 0 {
// Kick the scavenger awake if someone requested it.
wakeScavenger()
}
// 4) 检查所有的P查看是否存在运行时间太长的G需要设置抢占请求。
// 1. goroutine运行时间超过10ms时需要抢占。
// 2. goroutine陷入系统调用,运行时间超过10ms或在第二轮来是sysmon系统调用还没返回时。
// 陷入系统调用而抢占P的情况:
// 1. 运行时间超过10ms,可能一开始就陷入系统调用,或中途陷入系统调用。不论那种情况都应该抢占。
// 2. 运行时间没到10ms,但是两轮sysmon了还是在系统调用中,需要抢占P。这时候时间间隔在(0, 20ms)这个范围。
// retake P's blocked in syscalls
// and preempt long running G's
//
// 重新获取在系统调用中阻塞的 P 并抢占长时间运行的 G
// retake函数返回值,陷入系统调用的需要抢占的P的数量。
if retake(now) != 0 {
// 为什么陷入系统调度的P需要重置监控频率?
// 原因是陷入系统调度的P,把时间调回20us,下轮监控线程来时判断是否还在系统调用中。
idle = 0
} else {
idle++
}
// 5) GC相关,定时检查GC是否该触发了
// check if we need to force a GC
// t.test():判断是否满足定时GC间隔2分钟的条件
// forcegc.idle.Load():当前定时GC是空闲的
if t := (gcTrigger{kind: gcTriggerTime, now: now}); t.test() && atomic.Load(&forcegc.idle) != 0 {
lock(&forcegc.lock) // 获取互斥锁
forcegc.idle = 0 // 标记本轮定时GC开始了
var list gList // goroutine的列表,最后会把里面的goroutine放入P的本地队列
list.push(forcegc.g)// forcegchelper goroutine 加入本地队列,去触发goroutine
injectglist(&list) // list 加入P的本地队列等待M调用,等待forcegchelper()函数继续运行吧
unlock(&forcegc.lock)// 解锁
}
if debug.schedtrace > 0 && lasttrace+int64(debug.schedtrace)*1000000 <= now {
lasttrace = now
schedtrace(debug.scheddetail > 0)
}
unlock(&sched.sysmonlock)
}
}
|