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

Сколько тредов будет потреблять Go при GOMAXPROCS=1?

2.7 Senior🔥 211 комментариев
#Конкурентность и горутины#Основы Go#Производительность и оптимизация

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

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

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

Краткий ответ

При GOMAXPROCS=1 Go будет использовать только один системный поток для выполнения горутин, независимо от их количества. Однако важно понимать, что это не означает, что вся программа использует всего один поток — в системе будут дополнительные потоки для системных вызовов, сборщика мусора и других служебных задач.

Детальное объяснение

Основной принцип GOMAXPROCS

Параметр GOMAXPROCS определяет максимальное количество системных потоков (OS threads), которые могут одновременно выполняться для обработки горутин в рамках процесса Go. При значении 1:

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

Какие потоки все равно создаются

Даже при GOMAXPROCS=1 программа Go создает дополнительные потоки для:

  1. Системные вызовы (особенно блокирующие)

    // Пример: сетевой вызов создаст отдельный поток
    resp, err := http.Get("https://api.example.com")
    
    • При блокирующих системных вызовах runtime автоматически создает новый поток, чтобы не блокировать планировщик
  2. Сборщик мусора (GC)

    • Для параллельной сборки мусора создаются отдельные потоки
  3. CGO вызовы

    // Если используется CGO, создаются отдельные потоки
    // #include <stdio.h>
    // void process() { /* тяжелая C-функция */ }
    import "C"
    
    func main() {
        C.process() // Выполнится в отдельном потоке
    }
    
  4. Работа с сигналами

    • Отдельный поток для обработки POSIX-сигналов

Пример демонстрации

package main

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

func main() {
    runtime.GOMAXPROCS(1) // Устанавливаем один поток
    
    var wg sync.WaitGroup
    start := time.Now()
    
    for i := 0; i < 1000000; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            // Имитация работы
            _ = id * id
        }(i)
    }
    
    wg.Wait()
    fmt.Printf("Выполнено за %v с GOMAXPROCS=1\n", time.Since(start))
}

Критические особенности

Планирование при GOMAXPROCS=1

  • Round-robin планирование: горутины получают кванты времени по очереди
  • Точки вытеснения:
    • Каналы (channel operations)
    • Системные вызовы
    • Вызовы runtime.Gosched()
    • Сетевые операции
    • time.Sleep()

Производительность

// Плохой паттерн при GOMAXPROCS=1
func cpuBoundTask() {
    for i := 0; i < 1e9; i++ {
        // Долгие вычисления без точек вытеснения
    }
}

// Лучший подход
func betterTask() {
    for i := 0; i < 1000; i++ {
        doChunkOfWork()
        runtime.Gosched() // Явное освобождение процессора
    }
}

Практические рекомендации

  1. Когда использовать GOMAXPROCS=1:

    • Тестирование конкурентных алгоритмов в детерминированной среде
    • Отладка race conditions
    • Специфичные embedded-системы с одним ядром
  2. Современные настройки по умолчанию:

    // Начиная с Go 1.5, значение по умолчанию равно
    // количеству CPU ядер
    runtime.GOMAXPROCS(runtime.NumCPU())
    
  3. Мониторинг потоков:

    # Просмотр потоков процесса Go
    ps -T -p <PID>
    pstree -p <PID>
    

Вывод

Хотя GOMAXPROCS=1 ограничивает рабочие потоки планировщика до одного, общее количество потоков в процессе Go будет больше из-за служебных нужд runtime. Этот режим полезен для специфических сценариев, но в production-средах обычно используется значение по умолчанию, равное количеству CPU ядер, для максимальной производительности.