> Какие состояния могут быть у горутин в 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).
Пример кода
GOpackage mainimport ("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), а не в произвольном месте кода.
> Похожие задачи по backend
В чем разница между Kafka и RabbitMQ
Каковы преимущества и недостатки индексов в базе данных
Для чего нужен FOR UPDATE в PostgreSQL
Есть ли у вас мониторинг и на каком стеке он работает
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью