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

Как устроен планировщик (scheduler) в Go? Что такое модель M:N?

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

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

🐱
deepseek-v3.2PrepBro AI7 апр. 2026 г.(ред.)

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

Планировщик Go: архитектура и модель M:N

Планировщик Go — это встроенный планировщик потоков уровня пользователя, который реализует модель M:N, также известную как гибридная модель планирования. Это ключевой компонент рантайма Go, обеспечивающий конкурентность на основе горутин.

Модель M:N (гибридная модель)

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

  • 1:1 (один поток ОС на один поток пользователя) — как в Java/C++, создаёт высокие накладные расходы на переключение контекста.
  • N:1 (много потоков пользователя на один поток ОС) — не может использовать многоядерные процессоры.

Go использует M:N, чтобы получить преимущества обоих подходов: легковесность горутин и истинный параллелизм на многоядерных системах.

Ключевые компоненты планировщика Go

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

1. G (Goroutine) — горутина

Представляет собой легковесный поток, содержащий:

  • Стек (начинается с 2KB, динамически растёт/уменьшается)
  • Информацию о состоянии (ожидание, выполнение, завершена)
  • Программный счетчик
  • Указатель на каналы или мьютексы при блокировке
// Пример создания горутин
func main() {
    go func() { // Создаётся горутина G
        fmt.Println("Горутина 1")
    }()
    
    go worker() // Ещё одна горутина
    
    time.Sleep(100 * time.Millisecond)
}

func worker() {
    fmt.Println("Горутина 2")
}

2. M (Machine) — поток операционной системы

Представляет собой поток ядра ОС, который выполняет код Go. Каждый M привязан к одному потоку ОС. M отвечает за:

  • Выполнение кода горутин
  • Управление стеком
  • Взаимодействие с планировщиком

3. P (Processor) — виртуальный процессор

Концепция, введённая для улучшения масштабируемости. P представляет собой ресурс, необходимый для выполнения кода Go:

  • Очередь локальных горутин (runqueue)
  • Ссылку на текущую выполняемую горутину
  • Кэши объектов памяти (mcache)

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

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

  1. Инициализация: При запуске программы создаётся N потоков M (обычно равное GOMAXPROCS) и N процессоров P.

  2. Распределение: Каждый P связывается с M. Когда горутина создаётся, она помещается в локальную очередь P.

  3. Выполнение: M "крадет" горутину из очереди P и выполняет её.

  4. Блокировка: Если горутина блокируется (канал, системный вызов, мьютекс):

    • Текущий M освобождается от P
    • P ищет другой M или создаёт новый
    • Блокированная горутина помещается в соответствующую очередь ожидания
  5. Вытеснение: Планировщик использует кооперативную модель с элементами вытеснения:

    • Горутина добровольно уступает выполнение в определённых точках (каналы, вызовы функций)
    • Системный монитор (sysmon) вытесняет горутины, выполняющиеся слишком долго (>10ms)

Преимущества архитектуры

// Пример демонстрации масштабируемости
func main() {
    runtime.GOMAXPROCS(4) // 4 виртуальных процессора P
    
    for i := 0; i < 10000; i++ {
        go func(id int) {
            // 10000 горутин планируются на 4 потока ОС
            _ = fmt.Sprintf("Горутина %d", id)
        }(i)
    }
    
    time.Sleep(time.Second)
}

Ключевые преимущества:

  • Низкие накладные расходы: Переключение горутин происходит в пространстве пользователя (десятки наносекунд)
  • Автомасштабирование: Количество потоков M адаптируется под нагрузку
  • Работающий Steal алгоритм: Незанятые P могут "красть" горутины у занятых
  • Глобальная очередь: Балансировка нагрузки между разными P
  • Сетевой поллинг: Системные вызовы ввода-вывода эффективно обрабатываются через netpoller

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

Планировщик Go значительно развивался:

  • До Go 1.1: Глобальная очередь с большим мьютексом
  • Go 1.1: Введение P (Processor) для распределённых очередей
  • Go 1.14: Полноценное асинхронное вытеснение для снижения задержек

Планировщик Go — это сложная система, которая абстрагирует разработчика от управления потоками, обеспечивая эффективную конкурентность на многоядерных системах с минимальными накладными расходами. Модель M:N позволяет запускать миллионы горутин при управлении всего несколькими тысячами потоков ОС, что делает Go особенно эффективным для высоконагруженных конкурентных приложений.