Ответ
Планировщик Go использует модель кооперативной многозадачности. Это означает, что горутина уступает процессорное время добровольно в определённых точках выполнения. Переключение контекста (yield) происходит при:
- Блокирующих системных вызовах: Например, операции ввода-вывода (чтение файла, сетевой запрос).
- Операциях с каналами: Отправка или получение данных из канала, если он заблокирован.
- Вызовах
time.Sleep()
: Приостановка выполнения на заданное время. - Блокировках мьютексов: При попытке захватить уже заблокированный мьютекс.
- Явном вызове
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 потоков операционной системы, что позволяет эффективно управлять тысячами одновременных горутин.