> Почему в Go можно создать тысячи горутин (Go)

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

Компании: ЦУМ, VK, Ozon, Avito

Стек: Go

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

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

В Go можно создавать тысячи горутин, потому что они - это не потоки операционной системы, а легковесные пользовательские единицы выполнения, управляемые рантаймом. Каждая горутина изначально занимает всего несколько килобайт стека (обычно 2-8 КБ), который динамически растёт и сжимается. Рантайм распределяет горутины по небольшому числу потоков ОС (обычно GOMAXPROCS) через собственный планировщик, что позволяет создавать миллионы горутин без значительных накладных расходов на память и переключение контекста.

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

Горутина - это функция, которая выполняется конкурентно с другими горутинами в том же адресном пространстве процесса. Ключевое отличие от потоков ОС:

  • Поток ОС имеет фиксированный стек (обычно 1-8 МБ), создаётся и уничтожается системным вызовом, переключение контекста требует перехода в режим ядра и стоит дорого (микросекунды).
  • Горутина создаётся в пользовательском пространстве, её стек изначально крошечный (2-8 КБ) и растёт по мере необходимости (до 1 ГБ максимум). Переключение между горутинами - это просто сохранение и восстановление регистров и указателя стека, что занимает наносекунды.

Планировщик Go реализует модель M:N: M потоков ОС выполняют N горутин. Рантайм использует:

  • Локальные очереди для горутин на каждом потоке.
  • Глобальную очередь для горутин, ожидающих выполнения.
  • Work stealing - потоки забирают горутины из очередей других потоков для балансировки нагрузки.
  • Системные вызовы - если горутина блокируется на syscall, поток ОС может быть передан другой горутине, а заблокированная горутина переходит в специальное состояние.

Когда горутина блокируется на канале, мьютексе или таймере, она не занимает поток ОС - планировщик просто переключается на другую горутину. Это делает горутины идеальными для I/O-bound задач и высококонкурентных сервисов.

На практике

На практике это означает, что можно писать код, который создаёт десятки тысяч горутин без риска исчерпать память или упасть по лимиту процессов. Например, обработка каждого HTTP-запроса в отдельной горутине - стандартный паттерн в Go. При этом важно помнить:

  • Горутины не бесплатны: каждая занимает память под стек и структуру g (около 2-4 КБ служебных данных).
  • Нужно контролировать завершение горутин, чтобы избежать утечек (использовать context.Context, каналы, sync.WaitGroup).
  • Для ограничения параллелизма (например, при работе с внешними API) используют семафоры или worker pool.

Пример кода

GO
package main
import (
"fmt"
"sync"
)
func main() {
const n = 100000 // 100 тысяч горутин
var wg sync.WaitGroup
wg.Add(n)
for i := 0; i < n; i++ {
go func(id int) {
defer wg.Done()
// имитация полезной работы
_ = id * id
}(i)
}
wg.Wait()
fmt.Println("все горутины завершились")
}

Этот код создаёт 100 000 горутин и успешно завершается, потребляя всего несколько десятков мегабайт памяти. Аналогичный код с потоками ОС упал бы на лимите процессов или исчерпал память.

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

Начни с ключевого отличия: горутины - это не потоки ОС, а легковесные сущности рантайма. Упомяни размер стека (2-8 КБ, динамический рост) и стоимость переключения (наносекунды против микросекунд). Затем объясни модель планировщика: M:N, work stealing, локальные и глобальные очереди. Подчеркни, что блокировка на канале или мьютексе не занимает поток ОС. Если спросят про ограничения - скажи, что лимит упирается в память (примерно 2-4 КБ на горутину служебных данных) и что на практике миллионы горутин возможны, но обычно не нужны.

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

Интервьюер проверяет:

  • Понимание разницы между горутинами и потоками ОС.
  • Знание внутреннего устройства планировщика Go (M:N, work stealing).
  • Понимание trade-off: горутины дешевле, но не бесплатны.
  • Умение объяснить, почему горутины подходят для I/O-bound задач.
  • Осознание практических ограничений (память, утечки, контроль завершения).

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

  • Утверждение, что горутины - это просто потоки ОС, только легче.
  • Забывают упомянуть динамический размер стека.
  • Говорят, что горутины не имеют накладных расходов вообще.
  • Путают параллелизм (parallelism) и конкурентность (concurrency).
  • Не упоминают, что блокировки на каналах не занимают поток ОС.
  • Считают, что GOMAXPROCS - это максимальное количество горутин (на самом деле - количество потоков ОС для выполнения горутин).

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

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