← Назад к вопросам

Что отвечает за планирование ресурсов?

1.7 Middle🔥 171 комментариев
#Основы Go

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI4 апр. 2026 г.

Ответ сгенерирован нейросетью и может содержать ошибки

Роль планирования ресурсов в Go

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

Основные компоненты планера

Планер Go реализует модель M:N, где множество горутин (G) планируется на множество потоков операционной системы (M), которые выполняются на доступных ядрах процессора (P). Ключевые абстракции:

  • G (Goroutine) — легковесная виртуальная нить, единица планирования.
  • M (Machine) — поток ОС (kernel thread), который непосредственно выполняет код.
  • P (Processor) — логический процессор, ресурсный контекст для выполнения горутин.
// Упрощенное представление структур из runtime (для понимания модели)
type g struct {
    // стэк, состояние, приоритет и т.д.
}

type m struct {
    curg    *g     // текущая исполняемая горутина
    p       puintptr // привязанный P
}

type p struct {
    // локальная очередь горутин (runqueue)
    runq    [256]guintptr
    runqhead, runqtail uint32
}

Ключевые механизмы планирования

  1. Work Stealing (кража задач) — если у процессора (P) закончились горутины в локальной очереди, он может "украсть" половину задач из очереди другого процессора. Это обеспечивает эффективную балансировку нагрузки.

  2. Cooperative Scheduling (кооперативное планирование) — горутины сами "уступают" управление в точках:

    • Вызов runtime.Gosched()
    • Операции с каналами (send/receive)
    • Системные вызовы (когда горутина блокируется)
    • Сетевые операции
    • Сборка мусора
  3. Preemptive Scheduling (вытесняющее планирование) — начиная с Go 1.14, реализована асинхронная вытесняющая многозадачность на основе сигналов ОС. Планер может прервать длительно выполняющуюся горутину, чтобы дать время другим.

// Пример, где планирование играет ключевую роль
func main() {
    // Создаем 1000 горутин
    for i := 0; i < 1000; i++ {
        go func(id int) {
            for {
                // Долгая работа
                time.Sleep(10 * time.Millisecond)
                
                // Кооперативная точка - вызов функции
                doSomething()
                
                // Явное указание уступить (не всегда необходимо)
                runtime.Gosched()
            }
        }(i)
    }
    
    select {} // Вечное ожидание
}

Распределение по ядрам

Количество логических процессоров (P) по умолчанию равно количеству ядер CPU (определяется GOMAXPROCS). Каждый P имеет:

  • Локальную очередь горутин (ограниченного размера)
  • Глобальную очередь (для редко используемых или вновь созданных горутин)
  • Очередь выполнения для заблокированных и готовых к продолжению горутин

Эволюция планера

  • Go 1.0: простой планер с глобальной очередью и мьютексом
  • Go 1.1: внедрение модели MPG и work-stealing
  • Go 1.14: полноценная вытесняющая многозадачность
  • Go 1.20+: оптимизации под высоконагруженные сценарии

Настройка и отладка

Разработчики могут влиять на планирование:

  • runtime.GOMAXPROCS() — установка количества логических процессоров
  • go func() { ... }() — создание горутины
  • runtime.Gosched() — явное указание уступить
  • -trace и pprof для анализа производительности

Итог: Планер Go — это sophisticated система, которая автоматически распределяет тысячи горутин по доступным ядрам CPU, используя комбинацию work-stealing, кооперативного и вытесняющего планирования. Его основная цель — максимальная утилизация ресурсов процессора при минимальных накладных расходах на переключение контекста, что делает Go эффективным для concurrent-вычислений.