sysmon
sysmon — это общая функция, буквально переводится как системный монитор. Если удалить части с комментариями, это всего около 200 строк кода. Ему выделяется отдельный поток для запуска во время фазы bootstrap программы, и затем он непрерывно мониторит состояние runtime программы Go в фоновом режиме и выполняет соответствующую обработку. Код для его запуска можно посмотреть в функции runtime.main:
func main() {
...
mp := getg().m
mainStarted = true
systemstack(func() {
newm(sysmon, nil, -1)
})
...
}Сам системный монитор — это просто цикл for. Интервал между каждым раундом циклов — 20 микросекунд, и по мере роста индекса простоя программы время интервала увеличивается до максимума 10 миллисекунд. В каждом раунде цикла в основном выполняются следующие действия:
- Помощь в планировании goroutine, вытеснение долго работающих goroutine
- Проверка условий памяти и определение, нужна ли сборка мусора
- Проверка условий сетевого опросника и обработка goroutine, ожидающих по таймеру
- Очистка неиспользуемой памяти и возврат её операционной системе
- Сброс информации профиля кучи
- Сброс буферов mcache
Вытеснение
Работа вытеснения завершается функцией runtime.preemptone. Она в основном проверяет, превышает ли время выполнения текущей goroutine лимит. Если да, устанавливает поле preempt goroutine в true, чтобы указать, что она нуждается в вытеснении.
func preemptone(pp *p) bool {
mp := pp.m.ptr()
if mp == nil || mp == getg().m {
return false
}
gp := mp.curg
if gp == nil || gp == mp.g0 {
return false
}
gp.preempt = true
// Каждый вызов в goroutine должен теперь проверять вытеснение.
gp.stackguard0 = stackPreempt
return true
}Поле stackguard0 goroutine используется для проверки, нужно ли вытеснение. Когда оно установлено в stackPreempt, это указывает, что goroutine нуждается в вытеснении. Это механизм для проверок расширения стека. Когда стек goroutine нуждается в расширении, он устанавливает stackguard0 в stackPreempt. Когда goroutine вызывает функцию, она проверяет, равен ли stackguard0 stackPreempt. Если да, запускает расширение стека. Но если goroutine долго не вызывает функции, она не может запустить проверки расширения стека. Поэтому системный монитор должен проверять эту ситуацию.
func goschedImpl(gp *g, inheritTime bool) {
...
casgstatus(gp, _Grunning, _Grunnable)
if !inheritTime {
gp.schedtick++
}
globrunq.put(gp, 0)
schedule()
}Когда goroutine вытесняется, её статус меняется с _Grunning на _Grunnable, затем она помещается в глобальную очередь выполняемых для ожидания нового планирования.
Сборка мусора
Системный монитор периодически проверяет, нужна ли сборка мусора. Если время с последней сборки мусора превышает лимит, он принудительно запускает сборку мусора. Этот лимит времени определяется константой runtime.forcegcperiod, которая равна 2 минутам.
func sysmon() {
...
for {
...
// проверяем, нужна ли принудительная GC
if t := (gcTrigger{kind: gcTriggerTime, now: now}); t.test() && forcegc.idle.Load() {
lock(&forcegc.lock)
forcegc.idle.Store(false)
var list gList
list.push(forcegc.g)
injectglist(&list)
unlock(&forcegc.lock)
}
...
}
}Когда сборка мусора запущена, goroutine forcegc пробуждается для выполнения работы сборки мусора.
Сетевой опросник
Системный монитор также периодически проверяет условия сетевого опросника. Если есть goroutine, ожидающие по таймеру, он обрабатывает их. Эта часть работы завершается функцией runtime.netpoll.
func sysmon() {
...
for {
...
// опрос сети
npoll := netpoll(-1)
if npoll != 0 {
npollcnt++
}
...
}
}Когда у сетевого опросника есть goroutine, ожидающие по таймеру, они пробуждаются для продолжения выполнения.
Очистка памяти
Системный монитор также периодически очищает неиспользуемую память и возвращает её операционной системе. Эта часть работы завершается функцией runtime.mheap.scavenge.
func sysmon() {
...
for {
...
// очистка кучи
if scavengeHeap() {
scavengeCnt++
}
...
}
}Когда в куче есть неиспользуемая память, она возвращается операционной системе для уменьшения использования памяти.
Другая работа
Системный монитор также выполняет некоторую другую работу, такую как сброс информации профиля кучи, сброс буферов mcache и т.д. Эти задачи не являются основной работой системного монитора, но они также необходимы.
func sysmon() {
...
for {
...
// сброс профиля кучи
if shouldFlushHeapProfile() {
flushHeapProfile()
}
// сброс буферов mcache
if shouldDropMcache() {
dropMcache()
}
...
}
}В целом, системный монитор — это важная часть runtime Go. Он мониторит состояние runtime программы Go и выполняет соответствующую обработку для обеспечения стабильной работы программы Go.
