Что такое переключение контекста (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 эффективно управляет тысячами горутин, не создавая при этом избыточной нагрузки на операционную систему.

Ответ 18+ 🔞

Смотри, вот эта вся тема с переключением контекста в Go — это, блядь, как цирк с конями, только вместо лошадей у нас горутины, а вместо дрессировщика — планировщик, сука.

Короче, представь: планировщик — это такой злой диспетчер на заводе. Одна работяга-горутина начинает долбить кирпичи, а он её — хоп! — в сторонку. «Постой, дура, ты своё отработала». И запускает другую. Всё это дело называется переключением контекста.

Чем это, блядь, круто?

А круто тем, что наш диспетчер — не начальник из ОС. Он наш, доморощенный, в пользовательском пространстве работает. Представь разницу: позвать мастера из ЖЭКа (системный вызов) или самому гвоздь забить. Вот так и тут — наши переключения в сотни раз быстрее, наносекунды, ёпта! Никакой волокиты с ядром.

А когда этот диспетчер начинает буянить?

Он не спит, сука, следит:

  1. Работяга затупил. Горутина полезла, например, файл читать или спать легла (time.Sleep), или ждёт ответа из канала. Всё, диспетчер машет рукой: «Иди постой, пока». И запускает того, кто дело делать может.
  2. Работяга сам душой покривил. Вызвал runtime.Gosched() — типа «окей, я пас, пусть другие поработают». Редкий случай альтруизма в этом жестоком мире.
  3. Время вышло. У каждой горутины есть свой кусок времени, квант (~10 мс). Отпахала свой срок — свободен! Планировщик может её снять, даже если она ещё не закончила. Это называется вытесняющее планирование, и да, оно реально существует.

Что он при этом тащит с собой?

Ну, не весь станок же переносить. Сохраняет только самое нужное, по-минимуму:

  • Где остановился (указатель стека).
  • Что делать дальше (счётчик команд).
  • В каком состоянии инструменты (значения регистров).

Всё. Лёгкий такой чемоданчик. Поэтому и быстро.

Пример, чтобы вообще всё стало ясно

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 потоков). Операционная система даже не подозревает, какой у неё под носом цирк происходит, и не обосрётся от нагрузки. Красота!