> Что такое starvation локальной очереди и когда она возникает (Go)
Уровень: junior · Роль: backend · Язык: Go · Категория: Технические вопросы
Компании: amoCRM
Стек: Go
> Пример ответа
Короткий ответ
Starvation локальной очереди - это ситуация, когда горутина не получает задач из своей локальной очереди, потому что другие горутины постоянно "воруют" их или планировщик Go отдаёт предпочтение другим очередям. Возникает при интенсивной работе с go func() в циклах, когда задачи распределяются неравномерно, или при использовании runtime.Gosched() в неподходящих местах. В итоге некоторые горутины простаивают, а другие перегружены.
Подробное объяснение
Планировщик Go использует модель M:N - много горутин на несколько потоков ОС. У каждого потока (M) есть своя локальная очередь (local run queue) с горутинами, готовыми к выполнению. Когда локальная очередь пуста, планировщик пытается забрать горутины из глобальной очереди или "украсть" их у других потоков (work stealing).
Starvation локальной очереди возникает, когда:
-
Неравномерное распределение задач - одна горутина создаёт много дочерних задач, а другие - мало. Планировщик может постоянно "воровать" задачи у перегруженной очереди, оставляя другие очереди пустыми.
-
Долгие блокирующие операции - если горутина в очереди долго выполняет syscall или блокируется на канале, планировщик может переключиться на другую очередь, и задачи из первой будут ждать.
-
Частые
runtime.Gosched()- эта функция добровольно отдаёт процессор, но если её вызывать слишком часто, планировщик может постоянно перекидывать горутину в конец очереди, и она будет "голодать". -
Высокая конкуренция за локальные очереди - при большом количестве потоков и частом work stealing некоторые очереди могут оставаться без внимания.
Важно понимать: starvation в Go - это не ошибка, а особенность планировщика. Он старается балансировать нагрузку, но в крайних случаях может возникнуть дисбаланс.
На практике
На практике starvation локальной очереди редко проявляется в простых приложениях. Чаще всего она заметна в:
- Высоконагруженных сервисах с тысячами горутин
- Паттернах "воркер-пул" с неравномерной загрузкой
- Приложениях с интенсивным созданием горутин в циклах
Признаки starvation:
- Некоторые горутины выполняются дольше остальных
- Профилирование показывает, что часть потоков простаивает, а другие загружены
- Приложение работает медленнее, чем ожидалось, при достаточном количестве ядер
Решение обычно сводится к:
- Использованию буферизированных каналов для выравнивания нагрузки
- Ограничению количества одновременно создаваемых горутин
- Использованию
sync.WaitGroupили errgroup для контроля завершения
Пример кода
GOpackage mainimport ("fmt""runtime""sync")func main() {runtime.GOMAXPROCS(2) // ограничим для наглядностиvar wg sync.WaitGroupjobs := make(chan int, 100)// Создаём много горутин, которые постоянно создают новые задачиfor i := 0; i < 10; i++ {wg.Add(1)go func(id int) {defer wg.Done()for j := 0; j < 1000; j++ {jobs <- j// Имитация работы_ = id * j}}(i)}go func() {wg.Wait()close(jobs)}()// Потребительfor job := range jobs {_ = job}fmt.Println("Done")}
В этом примере 10 горутин пишут в один канал, и планировщик может неравномерно распределять их по очередям. Одна горутина может "украсть" задачи у другой, и та будет простаивать.
Как отвечать на собеседовании
Начни с определения: starvation - это когда горутина не получает процессорное время из-за особенностей планировщика. Затем объясни механизм: у каждого потока есть локальная очередь, и планировщик использует work stealing для балансировки. Упомяни, что starvation возникает при неравномерной нагрузке или частых переключениях контекста. Приведи пример из практики: воркер-пул с неравномерным распределением задач. Заверши тем, что это не баг, а особенность планировщика, и обычно решается структурными изменениями кода.
Что проверяет интервьюер
Интервьюер проверяет:
- Понимание устройства планировщика Go (M, P, G)
- Знание механизма work stealing
- Умение объяснять поведение конкурентного кода
- Понимание trade-off между производительностью и справедливостью
- Способность диагностировать проблемы производительности
Типичные ошибки
- Путать starvation с deadlock или livelock
- Считать, что starvation - это всегда ошибка в коде
- Не упоминать work stealing как ключевой механизм планировщика
- Предлагать
runtime.Gosched()как решение - это часто усугубляет проблему - Не различать starvation локальной очереди и глобальной очереди планировщика
> Похожие задачи по Go
На чем реализовывали поиск
Как происходит балансировка нагрузки в планировщике Go
Как масштабировать сервис коротких ссылок и настроить балансировку нагрузки
Подходит ли слайс в Go для хранения и частого увеличения массива событий
> Похожие задачи по backend
На чем реализовывали поиск
Как происходит балансировка нагрузки в планировщике Go
Что происходит, если по userId пользователь не найден?
Как масштабировать сервис коротких ссылок и настроить балансировку нагрузки
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью