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

Что произойдёт при запуске десяти тысяч потоков?

1.8 Middle🔥 151 комментариев
#Конкурентность и горутины#Производительность и оптимизация

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

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

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

При запуске десяти тысяч потоков (goroutines) в Go

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

Управление горутинами планировщиком Go

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

package main

import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 10000; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            fmt.Printf("Goroutine %d started\n", id)
        }(i)
    }
    wg.Wait()
    fmt.Println("All goroutines completed")
}

Ключевые процессы при запуске 10k горутин

  1. Эффективное создание горутин

    • Каждая горутина требует всего несколько килобайт памяти для начального стека (стек растет динамически). Создание 10k горутин не приведет к значительным затратам памяти сразу — это будет порядка десятков мегабайт, что вполне приемлемо для современной системы.
    • В отличие от системных потоков (которые требуют ~1-8 MB каждый), горутины очень экономичны.
  2. Работа планировщика Go

    • Планировщик будет распределять эти 10k горутин между M системными потоками, которые выполняются на GOMAXPROCS логических процессорах (обычно равно количеству ядер CPU).
    • Горутины будут выполняться конкурентно, но не обязательно параллельно — параллельное выполнение зависит от количества доступных CPU ядер.
  3. Возможные проблемы и ограничения

    • Нагрузка на планировщик: При таком количестве горутин планировщик может испытывать повышенную нагрузку на переключение контекста между горутинами.
    • Конкуренция за ресурсы: Если горутины активно работают (не просто sleep/wait), может возникнуть конкуренция за CPU, память, или другие ресурсы.
    • Блокировки: Если многие горутины одновременно пытаются получить доступ к общим ресурсам (каналам, mutex), могут возникнуть узкие места.

Реальные примеры и практические рекомендации

// Пример с потенциальной проблемой конкурентного доступа
package main

import (
    "sync"
    "time"
)

func main() {
    var counter int
    var mu sync.Mutex
    var wg sync.WaitGroup
    
    for i := 0; i < 10000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            mu.Lock()
            counter++
            mu.Unlock()
            time.Sleep(1 * time.Microsecond)
        }()
    }
    wg.Wait()
    fmt.Println("Counter:", counter)
}

Практические советы при работе с тысячами горутин:

  • Используйте пулы горутин или ограничивайте их количество с помощью семафоров или каналов, если задачи требуют интенсивных вычислений.
  • Для I/O-bound задач (сетевые запросы, работа с файлами) 10k горутин вполне нормально — они большую часть времени будут в состоянии ожидания.
  • Мониторьте использование памяти — хотя стеки горутин маленькие, если каждая горутина хранит большие данные в heap, память может быстро исчерпаться.
  • Используйте WaitGroup для контроля завершения всех горутин.

Сравнение горутин и системных потоков

ХарактеристикаГорутиныСистемные потоки
Потребление памяти~2-8 KB начального стека~1-8 MB
Создание/переключениеБыстрое, в runtime GoМедленное, требует ОС
ПараллелизмКонкурентность + параллелизм через планировщикПрямое параллельное выполнение

В итоге: запуск 10 тысяч горутин в Go вполне возможен и практикуется в реальных приложениях (например, в веб-серверах, обрабатывающих множество одновременных запросов). Однако необходимо учитывать характер задач — для CPU-bound операций такое количество может привести к снижению производительности из-за переключений контекста, а для I/O-bound задач это часто оптимальный подход.