Назад к вопросам
Middle+
106
questionbank

Что такое runtime в Go и как работает планировщик (scheduler)?

Sobes Copilot

Получайте ответы в реальном времени

Ответ от нейросети

sobes.tech AI

Runtime в Go - это среда выполнения, которая включает в себя:

  • Планировщик (scheduler): управляет горутинами и их выполнением на системных потоках.
  • Сборщик мусора (garbage collector): автоматически управляет памятью.
  • Системные вызовы: обертки над системными вызовами ОС.
  • Сетевой ввод/вывод: неблокирующее сетевое взаимодействие.

Планировщик Go реализует модель M:N, где M горутин выполняются на N системных потоках (OS threads). Используется трехкомпонентная модель G-P-M:

  • G (Goroutine): легковесный поток исполнения, управляемый планировщиком Go.
  • P (Processor): логический процессор, представляющий контекст, необходимый для выполнения горутин (очередь горутин, кэш стека). Количество P по умолчанию равно runtime.NumCPU().
  • M (Machine): системный поток (OS thread), на котором выполняется код.

Принцип работы планировщика:

  1. Создание горутин: Новые горутины создаются с помощью ключевого слова go.
  2. Помещение в очередь: Созданная горутина помещается в локальную очередь P или глобальную очередь, если локальная переполнена.
  3. Выполнение: M привязан к P и выбирает горутину из его локальной очереди (или глобальной, если локальная пуста). M выполняет код горутины.
  4. Переключение контекста: Происходит, когда горутина блокируется (например, при системном вызове, ожидании мьютекса, операции ввода-вывода) или когда планировщик решает переместить горутину (например, по истечении квоты времени).
    • Блокировка M: Если горутина вызывает блокирующий системный вызов, текущий M отключается от P, и планировщик находит или создает новый M для этого P. Заблокированный M возвращается в пул, когда системный вызов завершен.
    • Кооперативное вытеснение (Preemption): В старых версиях Go вытеснение было кооперативным (goroutine должна была явно уступить выполнение). В современных версиях (с 1.14) реализовано асинхронное вытеснение на основе сигналов ОС, что позволяет прерывать выполнение горутин, зациклившихся в неблокирующем коде.
  5. Балансировка нагрузки (Work Stealing): Если у одного P закончились горутины в локальной очереди, он может "украсть" горутины у других P или из глобальной очереди.

Преимущества планировщика Go:

  • Высокая производительность: Эффективно использует многоядерные процессоры.
  • Легковесные горутины: Позволяют создавать миллионы параллельно работающих "потоков".
  • Неблокирующий ввод/вывод: Упрощает написание высокопроизводительных сетевых приложений.
  • Простота использования: Разработчику не нужно управлять потоками вручную.

Пример создания горутины:

package main

import (
	"fmt"
	"time"
)

func worker() {
	fmt.Println("Выполняется горутина")
}

func main() {
	go worker() // Создание новой горутины
	time.Sleep(time.Second) // Ожидание завершения горутины
	fmt.Println("Завершена основная горутина")
}