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

Что такое планирование горутины?

2.7 Senior🔥 141 комментариев
#Конкурентность и горутины

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

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

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

Что такое планирование горутин (Goroutine Scheduling)?

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

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

Планировщик Go реализован как кооперативный планировщик с вытеснением в определённых точках. Он использует трёхуровневую архитектуру:

  • Горутины (G): Легковесные потоки выполнения, управляемые рантаймом Go.
  • Потоки ОС (M): Абстракция потоков ядра ОС (от слова machine).
  • Процессоры (P): Логические процессоры, которые связывают горутины и потоки ОС. Количество P обычно равно GOMAXPROCS (по умолчанию — количество CPU ядер).

Ключевые принципы работы:

  1. Кооперативное вытеснение: Горутина добровольно отдаёт управление в определённых точках вытеснения, таких как:

    • Вызовы функций (например, операции ввода-вывода, системные вызовы).
    • Канальные операции (chan send/receive).
    • Блокировки (mutex.Lock()).
    • Явный вызов runtime.Gosched().
    • Сетевые операции. Однако, начиная с Go 1.14, добавлено асинхронное вытеснение на основе сигналов ОС, чтобы предотвратить "захват" планировщика долго работающей горутиной.
  2. Работа ворк-стеaling (work stealing): Если у процессора (P) нет готовых к выполнению горутин, он может "украсть" половину задач из очереди другого процессора, что улучшает балансировку нагрузки.

  3. Глобальная очередь и локальные очереди: У каждого P есть локальная очередь (ограниченная по размеру) для быстрого доступа, а также существует глобальная очередь для новых горутин или перераспределения задач.

Пример базового планирования

package main

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

func main() {
    // Устанавливаем количество логических процессоров
    runtime.GOMAXPROCS(2)
    
    for i := 0; i < 5; i++ {
        go func(id int) {
            for j := 0; j < 3; j++ {
                fmt.Printf("Горутина %d: шаг %d\n", id, j)
                // Точка вытеснения: планировщик может переключиться на другую горутину
                runtime.Gosched()
            }
        }(i)
    }
    
    time.Sleep(time.Second) // Даём время на выполнение
}

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

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

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

Точки вытеснения и асинхронное планирование

До Go 1.14 планировщик полагался только на кооперативные точки вытеснения, что могло приводить к проблемам с горутинами, выполняющими долгие вычисления без вызовов функций. Сейчас планировщик использует сигналы OS (SIGURG), чтобы асинхронно прерывать выполнение и обеспечивать честное распределение CPU времени.

Практические аспекты для разработчика

  • Не полагайтесь на порядок выполнения: Планировщик не гарантирует порядок выполнения горутин, если нет явной синхронизации.
  • Используйте каналы и примитивы синхронизации для координации.
  • Избегайте долгих вычислений без точек вытеснения: Разбивайте на части или используйте runtime.Gosched() в циклах.
  • Настройка GOMAXPROCS: В большинстве случаев значение по умолчанию оптимально, но для IO-нагруженных приложений можно увеличить.

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

Что такое планирование горутины? | PrepBro