Как происходит балансировка нагрузки в планировщике Go
Что проверяют
Знаете ли вы модель GMP и понимаете ли, что планировщик Go балансирует нагрузку кражей работы, а не центральным распределением. Следом обычно спрашивают, что происходит при блокирующем системном вызове и как вытесняется горутина с бесконечным циклом.
Как отвечать
Три сущности:
- G: горутина, стек начинается с нескольких килобайт и растёт по мере нужды;
- M: поток ОС, который исполняет код;
- P: логический процессор, контекст для исполнения Go-кода. Их число равно
GOMAXPROCS, по умолчанию это число доступных CPU. С Go 1.25 на Linux значение учитывает лимит CPU из cgroup, что важно в контейнерах.
У каждого P своя локальная очередь на 256 горутин и слот runnext для только что созданной горутины, плюс есть общая глобальная очередь. Новая горутина попадает к тому P, где её создали; если локальная очередь полна, половина её содержимого уходит в глобальную.
Балансировка:
- work stealing: P с пустой очередью проверяет глобальную очередь и сетевой поллер, а потом крадёт половину горутин из очереди случайно выбранного соседнего P;
- справедливость: раз в 61 такт планирования P заглядывает в глобальную очередь даже при непустой локальной, чтобы горутины там не голодали;
- hand off: если M ушёл в блокирующий системный вызов, фоновый поток
sysmonотбирает у него P и отдаёт другому M, остальные горутины продолжают работать; - вытеснение:
sysmonпомечает горутину, работающую дольше 10 мс. С Go 1.14 вытеснение асинхронное, через сигнал, поэтому даже цикл без вызовов функций не занимает P навсегда.
Сетевые операции поток не блокируют: горутина паркуется, а готовность сокета отслеживает netpoller на epoll или kqueue.
Где спотыкаются
Отвечают про каналы и sync.WaitGroup. Это инструменты синхронизации, к тому, как планировщик распределяет горутины по потокам, они не относятся.
Считают, что GOMAXPROCS ограничивает число потоков. Он ограничивает число P, а потоков, застрявших в системных вызовах, может быть намного больше.
Описывают планировщик, который «следит за загрузкой и перекладывает горутины». Центрального перераспределения нет: простаивающий P сам ищет работу у соседей.