← Назад к вопросам

Какие знаешь причины, по которым горутина убирается из потока?

2.0 Middle🔥 112 комментариев
#Конкурентность и горутины#Операционные системы и Linux#Основы Go

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Причины вытеснения (preemption) горутины в Go

В языке Go механизм планирования (scheduler) использует кооперативную многозадачность с элементами вытеснения, что означает, что горутины обычно добровольно уступают поток, но существуют четкие ситуации, когда планировщик принудительно снимает горутину с выполнения. Вот основные причины:

1. Системные вызовы (Syscalls)

Когда горутина выполняет блокирующий системный вызов (например, чтение файла, сетевые операции), текущий поток M (machine thread) блокируется. Планировщик Go отсоединяет эту горутину от потока, чтобы поток мог обслуживать другие горутины. После завершения syscall горутина возвращается в очередь ожидания.

func readFile() {
    data, err := os.ReadFile("file.txt") // Блокирующий системный вызов
    // Горутина будет снята с потока на время чтения
}

2. Операции с каналами (Channel Operations)

Блокирующие операции отправки или получения через каналы заставляют горутину уступить поток. Планировщик переводит ее в состояние ожидания и выбирает другую горутину для выполнения.

ch := make(chan int)
go func() {
    value := <-ch // Горутина блокируется и снимается с потока
    // Поток освобождается для других горутин
}()

3. Вызов runtime.Gosched()

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

func worker() {
    for {
        // Тяжелые вычисления
        runtime.Gosched() // Явная уступка потока
    }
}

4. Работа со сборщиком мусора (GC)

Во время фазы STW (Stop-The-World) сборщика мусора все горутины приостанавливаются. После завершения GC горутины планируются заново, но уже необязательно на тех же потоках.

5. Кооперативная точка вытеснения (Cooperative Preemption Points)

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

func tightLoop() {
    for i := 0; i < 1e9; i++ {
        // Теперь в таких циклах есть встроенные точки вытеснения
        // Планировщик может прервать выполнение каждые ~10 мс
    }
}

6. Сетевые поллинг и I/O мультиплексирование

Сеть реализована через netpoller — механизм, который асинхронно обрабатывает I/O. Когда горутина ожидает сетевых данных, она регистрируется в netpoller и освобождает поток.

conn, _ := net.Dial("tcp", "example.com:80")
buf := make([]byte, 1024)
n, _ := conn.Read(buf) // Горутина блокируется в netpoller

7. Блокировки мьютексов (Mutex)

При попытке захватить уже занятый мьютекс горутина блокируется и снимается с потока до разблокировки мьютекса.

var mu sync.Mutex
mu.Lock()
// Другая горутина, вызвавшая mu.Lock(), будет заблокирована

8. Работа с таймерами и tickers

Операции time.Sleep(), <-time.After() переводят горутину в спящее состояние, освобождая поток на указанный период.

Как работает механизм вытеснения технически:

  • Каждые 10 мс планировщик получает сигнал SIGURG от отдельного потока монитора (sysmon).
  • Планировщик проверяет, не выполняется ли горутина дольше 10 мс (по умолчанию).
  • Если превышен лимит, горутина помечается для вытеснения через установку флага preempt в ее стеке.
  • При следующей безопасной точке (function prologue) выполнение прерывается.

Важные особенности:

  • Вытеснение происходит только в безопасных точках, где состояние горутины известно (обычно при вызове функций).
  • Горутины, выполняющие чистые вычисления (tight loops), до Go 1.14 могли вызывать "голодание" других горутин.
  • Механизм netpoller — ключевой для эффективного I/O, позволяющий миллионам горутин работать на небольшом числе потоков.

Практические следствия:

  • Не нужно вручную вызывать Gosched() в большинстве случаев
  • Долгие вычисления лучше разбивать на части или использовать контексты с таймаутами
  • Система спроектирована так, чтобы максимально утилизировать потоки и минимизировать простой

Понимание этих механизмов критично для написания эффективных конкурентных программ на Go и диагностики проблем с производительностью.

Какие знаешь причины, по которым горутина убирается из потока? | PrepBro