Как работает планировщик goroutine в Go (GMP модель)
Что проверяют
Понимаете ли вы, как горутины ложатся на потоки операционной системы, и знаете ли, что происходит при блокирующем вызове.
Как отвечать
Три сущности:
- G, горутина: сама задача со своим стеком;
- M, machine: поток операционной системы, который исполняет код;
- P, processor: контекст выполнения, право исполнять код Go. Их ровно
GOMAXPROCS, по умолчанию по числу ядер.
Чтобы выполнить горутину, потоку M нужен P. У каждого P своя локальная очередь готовых горутин плюс есть глобальная. Когда локальная очередь пустеет, P забирает половину задач у соседа, это work stealing, и он же выравнивает нагрузку без общей блокировки.
Что происходит при блокировке, это главная часть ответа. Горутина ушла в блокирующий системный вызов: M остаётся с ней, но отдаёт свой P, и другой поток подхватывает P с остальными горутинами. Работа не встаёт. Если же горутина ждёт сеть, её снимают с потока и передают сетевому опросчику (netpoller, epoll или kqueue), а M продолжает исполнять другие горутины. Поэтому тысячи одновременных соединений в Go стоят дёшево.
Планировщик вытесняющий: с версии 1.14 горутину прерывают асинхронно по сигналу, и бесконечный цикл без вызовов функций больше не может занять P навсегда.
Где спотыкаются
Называют буквы GMP, но не могут объяснить, что происходит при системном вызове.
Считают, что GOMAXPROCS ограничивает число потоков. Он ограничивает число P, потоков может быть заметно больше.
Не знают про netpoller и объясняют дешевизну сетевого ввода-вывода «легковесностью горутин».
Думают, что планировщик кооперативный, как было до 1.14.