> Что такое starvation локальной очереди и когда она возникает (Go)

Уровень: junior · Роль: backend · Язык: Go · Категория: Технические вопросы

Компании: amoCRM

Стек: Go

> Пример ответа

Короткий ответ

Starvation локальной очереди - это ситуация, когда горутина не получает задач из своей локальной очереди, потому что другие горутины постоянно "воруют" их или планировщик Go отдаёт предпочтение другим очередям. Возникает при интенсивной работе с go func() в циклах, когда задачи распределяются неравномерно, или при использовании runtime.Gosched() в неподходящих местах. В итоге некоторые горутины простаивают, а другие перегружены.

Подробное объяснение

Планировщик Go использует модель M:N - много горутин на несколько потоков ОС. У каждого потока (M) есть своя локальная очередь (local run queue) с горутинами, готовыми к выполнению. Когда локальная очередь пуста, планировщик пытается забрать горутины из глобальной очереди или "украсть" их у других потоков (work stealing).

Starvation локальной очереди возникает, когда:

  1. Неравномерное распределение задач - одна горутина создаёт много дочерних задач, а другие - мало. Планировщик может постоянно "воровать" задачи у перегруженной очереди, оставляя другие очереди пустыми.

  2. Долгие блокирующие операции - если горутина в очереди долго выполняет syscall или блокируется на канале, планировщик может переключиться на другую очередь, и задачи из первой будут ждать.

  3. Частые runtime.Gosched() - эта функция добровольно отдаёт процессор, но если её вызывать слишком часто, планировщик может постоянно перекидывать горутину в конец очереди, и она будет "голодать".

  4. Высокая конкуренция за локальные очереди - при большом количестве потоков и частом work stealing некоторые очереди могут оставаться без внимания.

Важно понимать: starvation в Go - это не ошибка, а особенность планировщика. Он старается балансировать нагрузку, но в крайних случаях может возникнуть дисбаланс.

На практике

На практике starvation локальной очереди редко проявляется в простых приложениях. Чаще всего она заметна в:

  • Высоконагруженных сервисах с тысячами горутин
  • Паттернах "воркер-пул" с неравномерной загрузкой
  • Приложениях с интенсивным созданием горутин в циклах

Признаки starvation:

  • Некоторые горутины выполняются дольше остальных
  • Профилирование показывает, что часть потоков простаивает, а другие загружены
  • Приложение работает медленнее, чем ожидалось, при достаточном количестве ядер

Решение обычно сводится к:

  • Использованию буферизированных каналов для выравнивания нагрузки
  • Ограничению количества одновременно создаваемых горутин
  • Использованию sync.WaitGroup или errgroup для контроля завершения

Пример кода

GO
package main
import (
"fmt"
"runtime"
"sync"
)
func main() {
runtime.GOMAXPROCS(2) // ограничим для наглядности
var wg sync.WaitGroup
jobs := 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 локальной очереди и глобальной очереди планировщика

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью