Какая модель планирования горутин используется в Go: конкурентная или вытесняющая
Что проверяют
Не путаете ли вы конкурентность как свойство программы с тем, как планировщик отбирает процессор у горутины. Правильный ответ: современный планировщик вытесняющий, с оговоркой про историю. Следом спрашивают про модель GMP и про горутину, которая крутит пустой цикл.
Как отвечать
Противопоставление «конкурентная или вытесняющая» некорректно, скорее всего имелось в виду «кооперативная». Конкурентность описывает программу, а вопрос о том, как планировщик переключает горутины:
- до Go 1.14: вытеснение было кооперативным. Горутина уступала процессор в точках планирования: при операциях с каналами, системных вызовах,
runtime.Gosched()и в прологе функций. Цикл без вызовов функций мог держать поток сколько угодно и задерживать сборку мусора; - с Go 1.14: появилось асинхронное вытеснение. Фоновый поток
sysmonзамечает горутину, которая работает дольше примерно 10 мс, и прерывает её сигналом (SIGURGна Unix-системах), после чего планировщик запускает другую.
Механику объясняет модель GMP:
- G: горутина с маленьким растущим стеком, стартует с пары килобайт;
- M: поток ОС;
- P: логический процессор с локальной очередью горутин. Их число задаёт
GOMAXPROCS, по умолчанию равное числу ядер. С Go 1.25 на Linux оно учитывает CPU-лимит cgroup, что важно в Kubernetes.
Когда локальная очередь P пуста, он крадёт работу у соседей (work stealing) и проверяет глобальную очередь. Блокирующий системный вызов отцепляет P от M, чтобы остальные горутины не стояли. Сетевые операции идут через netpoller и поток ОС не блокируют.
Где спотыкаются
Говорят, что Go кооперативный и цикл for {} навсегда занимает поток. Это было верно до 1.14.
Путают конкурентность и параллелизм: при GOMAXPROCS=1 горутины конкурентны, но параллельно не выполняются.
Думают, что вытеснение избавляет от синхронизации. Горутину могут прервать в любой точке, поэтому общие данные по-прежнему защищают sync.Mutex, атомарными операциями или каналами.