Что такое переключение контекста (Context Switching) в Go и как оно работает?

Ответ

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

Ключевые отличия от переключения на уровне ОС

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

Когда происходит переключение?

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

  1. Блокирующие операции: Системные вызовы (например, чтение файла), ожидание данных из канала, time.Sleep.
  2. Явный вызов: Вызов runtime.Gosched() добровольно уступает процессорное время другим горутинам.
  3. Истечение кванта времени: Каждая горутина выполняется не дольше определённого кванта времени (~10 мс). После этого планировщик может передать управление другой горутине (preemptive scheduling).

Что сохраняется?

При переключении сохраняется минимально необходимый контекст горутины:

  • Указатель стека (Stack Pointer): Где горутина остановила свое выполнение.
  • Счётчик команд (Program Counter): Какую инструкцию выполнять следующей.
  • Значения регистров: Текущее состояние регистров процессора для этой горутины.

Пример

func main() {
    go func() {
        fmt.Println("Горутина 1: начало")
        // Добровольно уступаем процессор, чтобы другая горутина могла выполниться
        runtime.Gosched()
        fmt.Println("Горутина 1: продолжение")
    }()

    go func() {
        fmt.Println("Горутина 2")
    }()

    time.Sleep(100 * time.Millisecond)
}

Благодаря модели M:N (M горутин на N потоков ОС), Go эффективно управляет тысячами горутин, не создавая при этом избыточной нагрузки на операционную систему.