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

Как переключается контекст между горутинами?

2.0 Middle🔥 81 комментариев
#Конкурентность и горутины#Производительность и оптимизация

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

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

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

Переключение контекста между горутинами в Go

В Go переключение контекста между горутинами — это процесс, при котором планировщик Go (scheduler) передает управление ЦПУ от одной выполняющейся горутины к другой. Это фундаментальный механизм, обеспечивающий конкурентное выполнение множества легковесных потоков.

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

Планировщик Go реализован в виде кооперативного планировщика на уровне пользователя (user-space cooperative scheduler). Он управляет горутинами, распределяя их по потокам ОС (worker threads). Вот ключевые принципы:

  • M:N планирование: Go использует модель M:N, где M горутин мультиплексируются на N потоках ОС.
  • Три основные сущности:
    • M (Machine): Поток ОС (kernel thread).
    • G (Goroutine): Сама горутина с её стеком и состоянием.
    • P (Processor): Логический процессор, связывающий M и G (содержит локальную очередь горутин).

Точки переключения контекста

Переключение контекста происходит в следующих ситуациях:

  1. Явные блокирующие операции:

    • Вызовы системных операций (сеть, файловый ввод-вывод).
    • Синхронизация (каналы, sync.Mutex, sync.WaitGroup).
    • Вызов runtime.Gosched().
  2. Неявные точки уступки:

    • При длительных вычислениях планировщик может прервать горутину после определенного времени.
    • При вызове функций, которые могут содержать проверки на перепланирование.
  3. Системные вызовы:

    • При блокирующем системном вызове поток ОС (M) может быть отсоединен от P, чтобы другой M мог использовать этот P.

Пример переключения при работе с каналами

package main

import (
    "fmt"
    "time"
)

func producer(ch chan<- int) {
    for i := 0; i < 3; i++ {
        ch <- i // Точка переключения: отправка в канал может заблокировать горутину
        fmt.Printf("Отправлено: %d\n", i)
    }
    close(ch)
}

func consumer(ch <-chan int) {
    for val := range ch {
        fmt.Printf("Получено: %d\n", val)
        time.Sleep(time.Second) // Точка переключения: sleep освобождает процессор
    }
}

func main() {
    ch := make(chan int, 2)
    go producer(ch)
    go consumer(ch)
    
    time.Sleep(5 * time.Second)
}

Внутренний механизм переключения

При переключении контекста планировщик выполняет:

  1. Сохранение состояния текущей горутины (регистры, стек, программа счетчик).
  2. Выбор следующей горутины из одной из очередей:
    • Локальная очередь P (локальные горутины)
    • Глобальная очередь (общие горутины)
    • Очередь ввода-вывода (горутины, ожидающие системных вызовов)
  3. Восстановление состояния выбранной горутины и передача ей управления.

Преимущества подхода Go

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

Отличие от потоков ОС

Важное отличие от потоков ОС: переключение контекста горутин не требует изменения контекста ядра, что делает его на порядки быстрее. В то время как переключение потоков ОС требует сохранения/восстановления всех регистров процессора и изменения таблиц памяти.

Роль runtime.Gosched()

Функция runtime.Gosched() явно уступает процессорное время другим горутинам:

func greedyGoroutine() {
    for i := 0; i < 5; i++ {
        // Длительная работа без точек уступки
        if i == 2 {
            runtime.Gosched() // Явно уступаем процессор
        }
    }
}

Оптимизации планировщика

Go постоянно улучшает планировщик. Например, начиная с Go 1.14, реализована вытесняющая многозадачность (preemptive scheduling), которая позволяет планировщику прерывать длительные вычисления без явных точек уступки, что уменьшает проблемы с "голоданием" горутин.

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

Как переключается контекст между горутинами? | PrepBro