Гарантирует ли Go вытесняющую многозадачность для горутин?

Ответ

Нет, не в классическом понимании. Планировщик Go использует модель кооперативной многозадачности с асинхронным вытеснением (начиная с Go 1.14).

1. Кооперативная многозадачность:

Планировщик Go передает управление другой горутине в так называемых "безопасных точках" (safe-points). Горутина добровольно уступает процессор, когда выполняет:

  • Операции с каналами (отправка или получение).
  • Системные вызовы (например, чтение файла или сетевой запрос).
  • Блокировки мьютексов.
  • Явный вызов runtime.Gosched().

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

// В версиях Go < 1.14 этот код мог заблокировать поток
func main() {
    go func() {
        // Бесконечный цикл без точек переключения
        for { }
    }()
    time.Sleep(time.Second)
    fmt.Println("Эта горутина может никогда не получить управление")
}

2. Асинхронное вытеснение (Go 1.14+):

Для решения проблемы долгих циклов в Go 1.14 была добавлена механика асинхронного вытеснения. Если горутина работает дольше определенного кванта времени (например, 10 мс), рантайм посылает ей сигнал, который приостанавливает ее выполнение и позволяет планировщику запустить другую горутину.

Это делает планировщик более справедливым и устойчивым, но runtime.Gosched() все еще остается полезным для тонкого управления и явной передачи управления в критических участках кода.