Как устроен планировщик (scheduler) в Go? Что такое модель M:N?
Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
Планировщик 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), но может меняться динамически.
Как работает планировщик
-
Инициализация: При запуске программы создаётся N потоков M (обычно равное GOMAXPROCS) и N процессоров P.
-
Распределение: Каждый P связывается с M. Когда горутина создаётся, она помещается в локальную очередь P.
-
Выполнение: M "крадет" горутину из очереди P и выполняет её.
-
Блокировка: Если горутина блокируется (канал, системный вызов, мьютекс):
- Текущий M освобождается от P
- P ищет другой M или создаёт новый
- Блокированная горутина помещается в соответствующую очередь ожидания
-
Вытеснение: Планировщик использует кооперативную модель с элементами вытеснения:
- Горутина добровольно уступает выполнение в определённых точках (каналы, вызовы функций)
- Системный монитор (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 особенно эффективным для высоконагруженных конкурентных приложений.