Ответ
Планировщик Go (Go Scheduler) может переключить контекст с одной горутины на другую (т.е. убрать её с системного потока) по нескольким причинам. Это ключевой механизм, обеспечивающий конкурентность в Go.
Основные причины:
-
Блокирующие системные вызовы (Syscalls) Когда горутина выполняет операцию, которая блокирует поток на уровне ОС (например, чтение файла, сетевой вызов), планировщик снимает её с потока, а на её место ставит другую готовую к выполнению горутину. Когда системный вызов завершается, горутина снова становится в очередь на выполнение.
-
Операции с каналами и мьютексами Горутина блокируется и уступает процессор, если:
- Пытается прочитать из пустого канала.
- Пытается записать в заполненный (небуферизованный или с переполненным буфером) канал.
- Пытается заблокировать уже захваченный мьютекс (
sync.Mutex
).
-
Вытесняющая многозадачность (Preemption) Начиная с Go 1.14, планировщик может принудительно вытеснять горутины, которые выполняются слишком долго без блокировок (например, в циклах сложных вычислений). Это предотвращает ситуацию, когда одна горутина монополизирует системный поток.
-
Явный вызов
runtime.Gosched()
Программист может явно попросить планировщик переключиться на другую горутину. Это добровольная уступка процессорного времени.go func() { fmt.Println("Другая горутина") }() fmt.Println("Текущая горутина уступает время...") runtime.Gosched() time.Sleep(100 * time.Millisecond) // Даем время другой горутине выполниться
-
Работа сборщика мусора (Garbage Collector) Во время определенных фаз сборки мусора (например, Stop-The-World) выполнение всех горутин приостанавливается.