В каких случаях горутина уступает процессор (переключается контекст)?

Ответ

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

Основные причины:

  1. Блокирующие системные вызовы (Syscalls) Когда горутина выполняет операцию, которая блокирует поток на уровне ОС (например, чтение файла, сетевой вызов), планировщик снимает её с потока, а на её место ставит другую готовую к выполнению горутину. Когда системный вызов завершается, горутина снова становится в очередь на выполнение.

  2. Операции с каналами и мьютексами Горутина блокируется и уступает процессор, если:

    • Пытается прочитать из пустого канала.
    • Пытается записать в заполненный (небуферизованный или с переполненным буфером) канал.
    • Пытается заблокировать уже захваченный мьютекс (sync.Mutex).
  3. Вытесняющая многозадачность (Preemption) Начиная с Go 1.14, планировщик может принудительно вытеснять горутины, которые выполняются слишком долго без блокировок (например, в циклах сложных вычислений). Это предотвращает ситуацию, когда одна горутина монополизирует системный поток.

  4. Явный вызов runtime.Gosched() Программист может явно попросить планировщик переключиться на другую горутину. Это добровольная уступка процессорного времени.

    go func() {
        fmt.Println("Другая горутина")
    }()
    
    fmt.Println("Текущая горутина уступает время...")
    runtime.Gosched()
    time.Sleep(100 * time.Millisecond) // Даем время другой горутине выполниться
  5. Работа сборщика мусора (Garbage Collector) Во время определенных фаз сборки мусора (например, Stop-The-World) выполнение всех горутин приостанавливается.