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

Что будет с потоком, если горутина заблокировалась?

2.0 Middle🔥 231 комментариев
#Конкурентность и горутины

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

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

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

Влияние блокировки горутины на потоки операционной системы

Когда горутина (goroutine) блокируется на операции ввода-вывода (например, чтение из сети, файла или канала), Go runtime корректно обрабатывает эту ситуацию, не блокируя поток операционной системы (ОС), на котором она исполняется. Вот как это работает:

Механизм работы Go scheduler

Go использует собственный планировщик (scheduler), который работает в пользовательском пространстве и управляет горутинами поверх потоков ОС (обычно называемых M — machine). Ключевые компоненты:

  1. G (goroutine) — сама горутина
  2. M (machine) — поток ОС
  3. P (processor) — логический процессор, который связывает M и G

Когда горутина блокируется на системном вызове (например, net.Read()):

package main

import (
    "net"
    "fmt"
)

func handleConnection(conn net.Conn) {
    buf := make([]byte, 1024)
    // Горутина блокируется здесь на чтении из сети
    n, err := conn.Read(buf) // Блокирующая операция
    if err != nil {
        return
    }
    fmt.Printf("Прочитано %d байт\n", n)
}

Что происходит при блокировке

  1. Горутина переходит в состояние ожидания — её статус меняется на Gwaiting.
  2. Текущий поток ОС (M) отсоединяется от заблокированной горутины и от своего логического процессора (P).
  3. P становится доступным для других потоков ОС.
  4. Другой поток ОС может взять этот P и продолжить выполнение других готовых горутин.
  5. Когда системный вызов завершается:
    • Заблокированная горутина помещается в локальную очередь готовых горутин
    • Она будет запланирована на выполнение, как только освободится P

Пример с каналами

Для операций с каналами механизм аналогичен:

func worker(ch chan int) {
    // Горутина блокируется здесь, пока в канале нет данных
    data := <-ch // Блокировка на операции чтения из канала
    fmt.Printf("Получено: %d\n", data)
}

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

  • Неблокирующие системные вызовы — Go runtime использует неблокирующие варианты системных вызовов через механизмы вроде epoll (Linux), kqueue (BSD), IOCP (Windows).
  • Сетевой ввод-вывод интегрирован в планировщик — с Go 1.5+ сетевые операции полностью интегрированы с планировщиком.
  • Количество потоков ОС может расти, но ограничено GOMAXPROCS и потребностями в блокирующих операциях.
  • Вытеснение (preemption) — начиная с Go 1.14, планировщик может вытеснять долго выполняющиеся горутины.

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

  • Масштабируемость — одна программа Go может эффективно обслуживать десятки тысяч одновременных соединений.
  • Эффективность — не создаются лишние потоки ОС для каждой горутины.
  • Прозрачность — разработчик пишет простой синхронный код, а runtime асинхронно выполняет его.

Вывод: Блокировка отдельной горутины не приводит к блокировке потока ОС благодаря умному планировщику Go, который динамически управляет сопоставлением горутин с потоками операционной системы. Это фундаментальная особенность, позволяющая Go эффективно работать с большим количеством одновременных соединений при относительно небольшом числе потоков ОС.