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

Сколько потоков запускается в Runtime приложения?

2.0 Middle🔥 91 комментариев
#Основы Go#Производительность и оптимизация

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

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

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

Сколько потоков запускается в Runtime Go?

Это один из фундаментальных вопросов о внутреннем устройстве Go. Прямого и фиксированного числа нет. Количество потоков операционной системы (далее — OS-потоков), которые создает и использует Go runtime, динамически изменяется в зависимости от нагрузки, конфигурации и выполняемого кода. Однако можно выделить ключевые компоненты и механизмы, которые создают эти потоки.

Основные категории потоков в Go Runtime

  1. Поток для выполнения goroutine (M)
    *   В модели планировщика Go (**GMP**: Goroutine, Machine (thread), Processor) `M` — это машина, представляющая собой OS-поток.
    *   Изначально при старте программы создается один такой поток для выполнения `main` goroutine.
    *   **Количество `M` динамически растет и сокращается** по мере необходимости. Если все существующие `M` заняты (например, они заблокированы на системных вызовах или ожидают работы), планировщик может создать новый `M`, чтобы продолжить выполнение готовых к работе goroutine. Максимальное количество `M` по умолчанию — 10 000 (управляется переменной `runtime/debug.SetMaxThreads`).

  1. Потоки для системных задач runtime
    *   **Сборщик мусора (GC)**: Для выполнения фоновой и параллельной сборки мусора GC использует выделенные OS-потоки. Их количество зависит от значения `GOMAXPROCS` и настроек GC (`GOGC`, `debug.SetGCPercent`).
    *   **Системный мониторинг (sysmon)**: Запускается специальный **неблокирующий системный поток** `sysmon`. Он не привязан к `P` и выполняет критически важные фоновые задачи:
        *   Вызывает сборщик мусора.
        *   Освобождает `P` и `M` от заблокированных на сетевых операциях goroutine, вытесняя их в отдельную сетевую подсистему.
        *   Вытесняет («preempt») goroutine, которые слишком долго выполняются, чтобы обеспечить справедливость планирования.

  1. Потоки, созданные для блокирующих системных вызовов и cgo
    *   Когда goroutine выполняет **блокирующий системный вызов** (например, файловый ввод-вывод, который не интегрирован в сетевой поллинг), runtime может временно отделить `M` от `P` и создать **новый `M`** для работы на освободившемся `P`. Это позволяет другим goroutine продолжать выполнение, не дожидаясь окончания блокирующей операции.
    *   Вызовы функций через **cgo** также выполняются в отдельном потоке, чтобы не блокировать планировщик Go.

  1. Потоки, созданные для netpoller (сетевого поллинга)
    *   Go использует асинхронную модель ввода+вывода для сетевых операций. **Netpoller** — это внутренний компонент, который, используя механизмы поллинга ОС (epoll, kqueue, IOCP), обрабатывает множество сетевых запросов.
    *   Изначально netpoller может использовать уже существующие потоки планировщика. Важно, что goroutine, ожидающая сетевых данных, **не блокирует поток (`M`)**, а лишь ставится в очередь ожидания события от netpoller.

Практический пример и вывод

Рассмотрим простую программу:

package main

import (
    "fmt"
    "runtime"
    "time"
)

func main() {
    fmt.Println("Количество логических CPU:", runtime.NumCPU())
    fmt.Println("Количество goroutine:", runtime.NumGoroutine())
    fmt.Println("Количество потоков (приблизительно): будет расти")

    // Запускаем много горутин, часть из которых будет блокироваться
    for i := 0; i <促00; i++ {
        go func(id int) {
            time.Sleep(10 * time.Second) // Блокирующая операция (sleep)
        }(i)
    }

    time.Sleep(2 * time.Second)
    // В этот момент runtime уже мог создать дополнительные потоки (M)
    // для обслуживания других горутин, пока часть 'M' занята sleep.
}

Итог: Количество потоков в Go runtime — это адаптивная величина. Минимально — это 1 поток для main + поток sysmon + возможно потоки для GC. Максимально — может достигать тысяч, если приложение активно использует блокирующиеся вызовы или CGO. Основная философия заключается в том, чтобы **маппить множество легковесных goroutine на меньшее число OS-

потоков**, эффективно управляя их созданием и утилизацией, что и обеспечивает высокую конкурентность при относительно низких накладных расходах.