Зачем использовать горутины вместо потоков в Go
Что проверяют
Знаете ли вы, что горутина не поток, а задача для рантайма Go, и как планировщик раскладывает горутины по потокам ОС. Следом почти всегда спрашивают про модель GMP и что происходит, когда горутина блокируется на системном вызове.
Как отвечать
Причин три: память, стоимость переключения и модель программирования.
- память: стек горутины стартует с 2 КБ и растёт по мере нужды, рантайм копирует его в больший участок. У потока ОС стек фиксированный, в Linux обычно 8 МБ виртуальной памяти. Поэтому сотни тысяч горутин это норма, а столько же потоков уже нет;
- создание и переключение: горутину создаёт и переключает рантайм в пространстве пользователя, без системного вызова. Переключение потоков идёт через ядро и стоит дороже;
- планировщик GMP: G это горутина, M поток ОС, P логический процессор с локальной очередью. Число P задаёт
GOMAXPROCS, по умолчанию это число доступных CPU, а с Go 1.25 учитывается и лимит CPU контейнера. Простаивающий P крадёт горутины из чужих очередей. Если горутина ушла в блокирующий системный вызов, P передаётся другому потоку, а сетевой ввод-вывод идёт через netpoller (epoll, kqueue) и потоки не занимает; - вытеснение: с Go 1.14 оно асинхронное, и тугой цикл без вызовов функций больше не подвешивает остальных.
Код при этом пишется в блокирующем стиле: go f(), каналы и пакет sync, без колбэков. Асинхронность обеспечивает рантайм.
Упомяните ограничение: горутины дёшевы, но не бесплатны. Для очереди задач ставят пул воркеров или ограничивают параллелизм через errgroup.SetLimit или семафор.
Где спотыкаются
Ждут завершения через time.Sleep. Правильно использовать sync.WaitGroup или errgroup.
Считают, что каналы сами избавляют от гонок. Общая структура, которую меняют из нескольких горутин без мьютекса, ломается так же, как в других языках. Ловить такое помогает go test -race.
Забывают про утечки: горутина, навсегда заблокированная на чтении из канала, живёт до конца программы. Выход для неё предусматривают через context.Context.