Какие основные состояния есть у горутины и в чем разница между Runnable и Waiting?

Ответ

У горутины есть три основных состояния с точки зрения планировщика Go:


  1. Running (Выполняется) — горутина в данный момент исполняет свой код на логическом процессоре (P), который привязан к потоку операционной системы (M).



  2. Runnable (Готова к выполнению) — горутина готова исполнять код, но ожидает своей очереди на свободный логический процессор. Такие горутины находятся в локальной или глобальной очереди планировщика.


  3. Waiting (Ожидание) — горутина заблокирована и не может выполняться. Она не потребляет ресурсы CPU и ждет внешнего события для разблокировки. Причины блокировки:

    • Системный вызов (например, чтение файла с диска).
    • Ожидание на канале (<-ch).
    • Ожидание мьютекса (mutex.Lock()).
    • Ожидание таймера (time.Sleep()).

Ключевая разница между Runnable и Waiting:

  • Runnable — это активное ожидание. Горутина хочет и может работать, но ждет процессорного времени.
  • Waiting — это пассивное ожидание. Горутина не может работать, пока не выполнится внешнее условие (например, придут данные в канал).
СостояниеОписаниеПотребление CPUПример
RunnableГотова к выполнению, в очереди планировщикаНетГорутина, только что созданная через go, или та, что вернулась из состояния Waiting
WaitingЗаблокирована (канал, мьютекс, syscall)Нетtime.Sleep(), <-ch, mutex.Lock()
RunningВыполняется на процессореДаИсполнение простого кода, например, a := 1 + 1
func main() {
    ch := make(chan int)

    go func() {
        // Сразу после запуска горутина становится Runnable.
        // Затем планировщик переводит ее в Running.

        time.Sleep(time.Second) // Переходит в Waiting на 1 секунду.

        // После пробуждения снова становится Runnable, а затем Running.
        ch <- 1 // Может перейти в Waiting, если канал небуферизованный и нет читателя.
    }()

    // Основная горутина переходит в Waiting, пока не получит данные из канала.
    <-ch 
}