Какие события приводят к переключению горутин? (Когда планировщик Go переключает контекст?)

Ответ

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

1. Кооперативное переключение (Voluntary Yielding)

Горутина добровольно отдает управление планировщику, когда выполняет операцию, которая может занять время.

  • Блокирующие системные вызовы: операции ввода-вывода (I/O), такие как чтение/запись в файлы или сетевые сокеты.
  • Операции с каналами: отправка или чтение из канала, если он заблокирован (например, отправка в полный канал или чтение из пустого).
  • Операции с примитивами синхронизации: блокировка мьютекса (sync.Mutex), ожидание на sync.WaitGroup.
  • Вызов time.Sleep: горутина "засыпает" на указанное время.
  • Явный вызов runtime.Gosched(): прямое указание планировщику немедленно переключиться на другую горутину.

2. Принудительное переключение (Preemptive Scheduling)

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

  • Истечение кванта времени: если горутина выполняется непрерывно (например, в долгом цикле без блокирующих вызовов) дольше определённого кванта времени (~10 мс), планировщик может её прервать и передать управление другой горутине. Это предотвращает "зависание" одного из системных потоков из-за одной долгой горутины.
  • Вызовы функций: компилятор вставляет специальные проверки в прологи функций. Если планировщик пометил горутину для прерывания, эта проверка вызовет переключение контекста.