> Какие состояния могут быть у горутин в Go (Go)

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

Компании: amoCRM, Wildberries

Стек: Go

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

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

У горутин в Go есть три основных состояния: running, runnable и waiting (в терминах планировщика - _Grunning, _Grunnable, _Gwaiting). Также существуют вспомогательные состояния: dead, syscall, init и idle. Горутина не имеет состояния blocked в привычном смысле - блокировка на каналах, мьютексах или таймерах реализуется через состояние waiting с указанием причины.

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

Планировщик Go (runtime scheduler) использует конечный автомат для управления горутинами. Основные состояния:

  • _Grunning - горутина выполняется на ядре операционной системы (M). В каждый момент времени на одном P может быть только одна горутина в этом состоянии.
  • _Grunnable - горутина готова к выполнению, находится в очереди run queue (локальной или глобальной). Планировщик выберет её при следующем переключении контекста.
  • _Gwaiting - горутина заблокирована и ждёт какого-то события: чтения из канала, захвата мьютекса, завершения syscall, таймера, GC-фазы и т.д. Причина ожидания хранится в поле waitreason.

Вспомогательные состояния:

  • _Gdead - горутина завершилась, её стек и ресурсы освобождены (или готовы к переиспользованию).
  • _Gsyscall - горутина выполняет системный вызов. В этом состоянии она не привязана к P, а P может быть передан другой горутине.
  • _Gidle - горутина создана, но ещё не инициализирована (редко встречается на практике).
  • _Gcopystack - временное состояние при копировании стека (stack growth).

Важно понимать: переходы между состояниями происходят только в определённых точках - при вызове функций runtime, при работе с каналами, при syscall и при вытеснении (preemption). Горутина не может быть принудительно остановлена в произвольном месте без cooperation.

На практике

Для разработчика знание состояний важно при:

  • Диагностике deadlock - если все горутины в состоянии waiting, программа зависнет.
  • Профилировании - runtime.NumGoroutine() показывает общее количество, но не состояние. Для детального анализа используйте pprof.Lookup("goroutine").
  • Проектировании конкурентных структур - понимание, что блокировка на канале не занимает поток ОС, позволяет создавать тысячи горутин без проблем.
  • Отладке - стек-трейс паники показывает состояние каждой горутины (например, chan receive или sync.Mutex.Lock).

Пример кода

GO
package main
import (
"fmt"
"runtime"
"time"
)
func main() {
var goroutines int
// Горутина в состоянии running (выполняется)
go func() {
for {
runtime.Gosched() // даём возможность другим горутинам работать
}
}()
// Горутина в состоянии waiting (ждёт на канале)
ch := make(chan struct{})
go func() {
<-ch // блокировка на чтении из канала
}()
// Горутина в состоянии runnable (готова, но ждёт планировщика)
go func() {
time.Sleep(10 * time.Millisecond)
}()
time.Sleep(50 * time.Millisecond)
// Получаем статистику по горутинам
buf := make([]byte, 1<<20)
n := runtime.Stack(buf, true)
fmt.Printf("Total goroutines: %d\n", runtime.NumGoroutine())
fmt.Printf("Stack trace (first 500 bytes):\n%s\n", buf[:min(n, 500)])
// В выводе будет видно:
// - goroutine в состоянии running (текущая main)
// - goroutine в состоянии chan receive (waiting)
// - goroutine в состоянии sleep (waiting)
// - goroutine в состоянии runnable (если планировщик ещё не запустил её)
_ = goroutines
}
func min(a, b int) int {
if a < b {
return a
}
return b
}

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

Начните с трёх основных состояний, затем упомяните вспомогательные. Подчеркните, что горутина не блокирует поток ОС в состоянии waiting - это ключевое отличие от потоков. Упомяните, что переключение контекста происходит только в точках cooperative scheduling (каналы, syscall, runtime.Gosched, вытеснение по таймеру). Если спросят про GOMAXPROCS - свяжите с количеством горутин в состоянии running одновременно.

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

  • Понимание модели планировщика Go (M:N scheduling).
  • Знание, что блокировка горутины не равна блокировке потока.
  • Умение объяснить разницу между waiting и runnable.
  • Понимание, как работает preemption в Go (начиная с Go 1.14 - асинхронное вытеснение).
  • Способность связать состояния с реальными проблемами (deadlock, starvation).

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

  • Путают состояния горутин с состояниями потоков ОС (например, говорят "горутина в состоянии blocked" - в Go такого состояния нет, есть waiting).
  • Утверждают, что горутина в состоянии waiting освобождает P - это не всегда так: при ожидании на канале P остаётся занят, если нет других горутин в очереди.
  • Не упоминают состояние _Gsyscall - оно критично для понимания, почему syscall может заблокировать весь поток.
  • Считают, что runtime.Gosched() переводит горутину в waiting - на самом деле она становится runnable.
  • Забывают, что горутина в состоянии running может быть вытеснена только в безопасной точке (safe point), а не в произвольном месте кода.

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

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