> Какое примерное количество горутин можно запустить на компьютере с 16 ГБ оперативной памяти? (Go)

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

Компании: Aston

Стек: Go

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

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

На машине с 16 ГБ RAM можно запустить порядка 1-3 миллионов горутин без немедленного падения, но практически разумный предел - 100-500 тысяч. Горутина изначально занимает ~2-4 КБ стека, плюс накладные расходы на планировщик и GC. При 16 ГБ это даёт теоретический потолок около 4-8 миллионов, но реальные ограничения - это не память, а производительность планировщика, частота переключений контекста и давление на GC.

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

Базовая математика: 16 ГБ = 16 × 1024³ байт ≈ 17.2 миллиарда байт. Если одна горутина занимает минимум 2 КБ стека, то 17.2e9 / 2e3 ≈ 8.6 миллионов. Но это только стек, без учёта:

  • Стек растёт динамически - реальные горутины часто занимают 8-32 КБ после первых вызовов функций.
  • GC-метаданные - каждая горутина добавляет объекты в heap, которые должен сканировать сборщик мусора.
  • Планировщик - runtime хранит очередь runnable горутин, и при миллионах горутин планировщик начинает деградировать: contention на глобальной очереди, кэш-промахи, рост latency.
  • Системные лимиты - ОС ограничивает количество потоков, но горутины не привязаны к потокам 1:1, поэтому это не жёсткий лимит, но планировщик всё равно тратит CPU на управление.

Практический ориентир: на 16 ГБ можно безопасно запустить ~500 тысяч горутин, если они в основном спят или ждут I/O. Если горутины активно работают и конкурируют за CPU, разумный предел - 50-100 тысяч, иначе начнётся серьёзная деградация производительности.

На практике

В реальных системах миллионы горутин - это антипаттерн. Обычно это означает, что архитектура неправильная: вместо горутины на задачу лучше использовать worker pool с каналами. При проектировании стоит исходить из правила: количество горутин должно быть пропорционально количеству конкурентных задач, а не объёму данных.

Для оценки на конкретной машине лучше написать бенчмарк: запустить N горутин, которые просто блокируются на канале, и измерить потребление RSS и время создания. Это даст точную цифру для вашего окружения.

Пример кода

GO
package main
import (
"fmt"
"runtime"
"sync"
"time"
)
func main() {
var wg sync.WaitGroup
const n = 1_000_000 // 1 млн горутин
start := time.Now()
for i := 0; i < n; i++ {
wg.Add(1)
go func() {
defer wg.Done()
runtime.Gosched() // минимальная работа
}()
}
wg.Wait()
var m runtime.MemStats
runtime.ReadMemStats(&m)
fmt.Printf("Создано %d горутин за %v\n", n, time.Since(start))
fmt.Printf("HeapAlloc: %.2f MB\n", float64(m.HeapAlloc)/1024/1024)
}

На 16 ГБ этот код скорее всего завершится успешно, но займёт несколько секунд и покажет HeapAlloc в районе 200-500 МБ.

Как отвечать на собеседовании

Начните с цифры, затем объясните, почему это не единственный критерий. Подчеркните, что важнее не абсолютное число, а понимание trade-off между памятью, CPU и latency. Упомяните, что на практике вы бы измерили это бенчмарком, а не полагались на теоретические расчёты. Если спросят про оптимизацию - скажите про worker pool, semaphore или errgroup с ограничением конкурентности.

Что проверяет интервьюер

  • Понимание устройства горутин: стек, планировщик, GC.
  • Умение делать быстрые оценки (estimation) и обосновывать их.
  • Знание практических ограничений runtime, а не только теории.
  • Способность рассуждать о масштабировании конкурентности в реальных системах.

Типичные ошибки

  • Называть точное число без оговорок - например, "ровно 8 миллионов", не учитывая рост стека и GC.
  • Путать горутины с потоками ОС и говорить про лимит на количество потоков.
  • Игнорировать CPU-bound сценарии, где лимит упирается в планировщик, а не в память.
  • Предлагать запускать миллионы горутин как норму - это сразу показывает непонимание практики.

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

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