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

Что такое scheduler?

1.0 Junior🔥 174 комментариев
#Конкурентность и горутины#Основы Go

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

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

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

Что такое Scheduler (Планировщик)?

Scheduler (планировщик) — это ключевой компонент среды выполнения Go, ответственный за многопоточное выполнение горутин на ограниченном количестве потоков операционной системы (OS threads). Его основная задача — эффективно распределять время процессора между тысячами или даже миллионами легковесных горутин, обеспечивая конкурентное выполнение кода.

В отличие от планировщика ОС, который работает с потоками (тяжелыми сущностями, требующими значительных ресурсов на создание и переключение контекста), планировщик Go работает на уровне пользователя (user-space) и управляет легковесными горутинами внутри одного или нескольких потоков ОС.

Основные компоненты и принципы работы

Планировщик Go реализован как кооперативный планировщик с вытеснением, основанный на модели M:N. Это означает:

  • M (Machine) — поток ОС (OS thread), который выполняет код.
  • P (Processor) — виртуальный процессор, контекст выполнения, который связывает горутины с потоком ОС. Количество P обычно равно GOMAXPROCS (по умолчанию — количество логических ядер CPU).
  • G (Goroutine) — собственно горутина, легковесный поток.
// Упрощенная визуализация связей
// M (OS Thread) <-> P (Processor) <-> Очередь из G (Goroutines)
//              +-> G (выполняющаяся горутина)

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

  1. Работа в очереди (Local/Global Run Queues):
    - Каждый **P** имеет свою локальную очередь (runqueue) из готовых к выполнению горутин.
    - Также существует глобальная очередь, куда попадают новые горутины или горутины из системных вызовов, если локальная очередь переполнена.

  1. Кооперативное вытеснение (Cooperative Preemption):
    - Горутина добровольно уступает поток, встречая определенные точки в коде:
        - Канальные операции (`chan send/receive`), которые блокируют.
        - Системные вызовы (например, файловый ввод/вывод).
        - Вызов `runtime.Gosched()`.
        - Сетевые операции (через netpoller).
    - С версии Go 1.14 также реализовано **асинхронное вытеснение на основе сигналов**, которое позволяет планировщику принудительно "снять" горутину, если она выполняется слишком долго (например, в длительном цикле без точек вытеснения), предотвращая "голодание" других горутин.

  1. Сетевой поллер (Netpoller):
    - Интегрирован в планировщик. Когда горутина выполняет блокирующую сетевую операцию, она не блокирует поток ОС. Вместо этого планировщик регистрирует запрос в netpoller и переключается на выполнение другой горутины. Когда сетевая операция готова, соответствующая горутина возвращается в очередь на выполнение.

Пример, демонстрирующий работу планировщика

package main

import (
    "fmt"
    "runtime"
    "time"
)

func main() {
    // Устанавливаем использование 1 логического процессора для наглядности
    runtime.GOMAXPROCS(1)

    go func() {
        for i := 0; i < 5; i++ {
            fmt.Println("Горутина 1:", i)
            // Точка вытеснения: добровольно уступаем место
            runtime.Gosched()
        }
    }()

    go func() {
        for i := 0; i < 5; i++ {
            fmt.Println("Горутина 2:", i)
        }
    }()

    // Даем время горутинам выполниться
    time.Sleep(time.Second)
}

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

Почему это важно?

  • Эффективность: Планировщик минимизирует затратные переключения контекста на уровне ОС, управляя всем в пространстве пользователя.
  • Масштабируемость: Позволяет легко создавать десятки тысяч конкурентных сущностей (горутин) без деградации производительности.
  • Простота моделирования конкурентности: Программист пишет последовательный код внутри горутины, а планировщик заботится о параллельном выполнении.

Планировщик Go — это интеллектуальный "дирижер", который делает возможной знаменитую простоту конкурентного программирования на Go, скрывая всю сложность управления потоками, очередями и системными событиями. Его эволюция (особенно введение асинхронного вытеснения) продолжает устранять редкие, но возможные в прошлом проблемы, такие как "голодание" горутин в CPU-связанном коде.

Что такое scheduler? | PrepBro