В каких случаях планировщик Go переключает контекст между горутинами?

Ответ

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

  1. Блокирующих системных вызовах: Например, операции ввода-вывода (чтение файла, сетевой запрос).
  2. Операциях с каналами: Отправка или получение данных из канала, если он заблокирован.
  3. Вызовах time.Sleep(): Приостановка выполнения на заданное время.
  4. Блокировках мьютексов: При попытке захватить уже заблокированный мьютекс.
  5. Явном вызове runtime.Gosched(): Это прямая команда планировщику переключиться на другую горутину.

Важное дополнение (с Go 1.14):
Для предотвращения "зависания" планировщика на долго выполняющихся циклах без точек переключения, в Go была введена асинхронная преемпция (asynchronous preemption). Планировщик может принудительно остановить горутину, которая выполняется слишком долго, и передать управление другим.

package main

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

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

    go func() {
        fmt.Println("Горутина 2: выполняется")
    }()

    time.Sleep(100 * time.Millisecond)
}

Планировщик Go работает по модели M:N, распределяя M горутин на N потоков операционной системы, что позволяет эффективно управлять тысячами одновременных горутин.