Кто управляет горутинами? Какой тип многозадачности используется в Go и какой был до версии Go 1.15?go-71

1. Кто управляет горутинами?

Горутинами управляет планировщик Go (scheduler), который является частью runtime-окружения языка. Это не планировщик операционной системы, а специальный механизм самого Go.

Архитектура планировщика:

  • G - Горутина (Goroutine)
  • M - Поток машины (OS Thread)
  • P - Процессор (Processor, логический контекст)
[Горутины (G)] --> [Процессоры (P)] --> [Потоки ОС (M)]

Планировщик использует модель M:N, где:

  • Множество горутин (N) распределяется по
  • Множеству потоков ОС (M), которые привязаны к
  • Логическим процессорам (P) в количестве GOMAXPROCS

2. Тип многозадачности в Go

После Go 1.14 :

Гибридная многозадачность:

  1. Кооперативная (основной режим):

    • Горутина добровольно освобождает ресурсы при:
      • Вызове блокирующих операций (каналы, syscalls)
      • Явном вызове runtime.Gosched()
      • Ожидании мьютексов
  2. Вытесняющая (добавлена в Go 1.14):

    • Планировщик принудительно прерывает долго работающие горутины (```10мс CPU времени)
    • Реализовано через системные таймеры

Пример вытеснения:

func longRunning() {
    for i := 0; i < 1000000000; i++ {
        // Долгие вычисления без точек вытеснения
    }
}
// После 10мс выполнения планировщик может прервать эту горутину

До Go 1.14:

Чисто кооперативная многозадачность:

  • Горутина могла выполняться неограниченно долго, пока сама не отдаст управление
  • Это приводило к проблемам:
    • "Голодание" других горутин
    • Задержки в обработке событий
    • Потенциальные лаги в реальном времени

Пример проблемы до 1.14:

func greedy() {
    for {
        // Без вызовов, которые передают управление
    }
}
// В версиях до 1.14 эта горутина могла заблокировать весь поток

3. Как работает планировщик

  1. Инициализация:

    • Создается M потоков ОС (по числу P)
    • Каждый P имеет локальную очередь горутин
  2. Распределение:

    • Горутина попадает в:
      • Локальную очередь своего P
      • Глобальную очередь (если локальная переполнена)
      • Очередь воровства (work-stealing)
  3. Вытеснение:

    • Мониторинг времени выполнения
    • Сигнал SIGURG для прерывания

4. Практическое влияние

После 1.14:

// Теперь безопасно делать:
go func() {
    for {
        heavyComputation() // Не заблокирует другие горутины надолго
    }
}()

До 1.14 требовалось:

go func() {
    for {
        heavyComputation()
        runtime.Gosched() // Явное указание планировщику
    }
}()

5. Настройка и мониторинг

  • GOMAXPROCS: Управляет количеством P

    runtime.GOMAXPROCS(4) // 4 логических процессора
    
  • Отладка:

    GODEBUG=schedtrace=1000 ./program # Трассировка планировщика
    

Резюмируем

  1. Управление: Горутинами управляет планировщик Go runtime (не ОС)
  2. Тип многозадачности:
    • До 1.14: Чисто кооперативная (требовала осторожности)
    • После 1.14: Гибридная (кооперативная + вытеснение долгих горутин)
  3. Архитектура: M:N модель с work-stealing планировщиком
  4. Практика: После 1.14 разработчику реже нужно думать о явной передаче управления

Эволюция планировщика Go сделала конкурентное программирование более надежным и предсказуемым.