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

Что нужно учитывать при старте горутины?

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

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

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

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

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

Управление жизненным циклом и утечки

Самая распространённая ошибка — запуск горутины без контроля её завершения. Это может привести к утечкам памяти и ресурсов.

func riskyStart() {
    go func() {
        // Работа без возможности остановки
        for {
            select {
            case <-time.After(time.Second):
                // Какая-то работа
            }
        }
    }()
}

Для управления используйте:

  • Контексты (context.Context) для отмены
  • Каналы для сигналов завершения
  • WaitGroup для ожидания группы горутин
func safeStart(ctx context.Context) {
    go func() {
        defer fmt.Println("Горутина завершена")
        for {
            select {
            case <-ctx.Done():
                return
            case <-time.After(time.Second):
                // Полезная работа
            }
        }
    }()
}

Паника и её обработка

Паника в горутине, если её не обработать, приводит к аварийному завершению всей программы. Всегда используйте recover в точках входа.

func safeWorker() {
    defer func() {
        if r := recover(); r != nil {
            log.Printf("Паника обработана: %v", r)
        }
    }()
    // Работа горутины
    panic("критическая ошибка")
}

Проблемы конкурентного доступа

Горутины часто работают с общими данными. Без синхронизации это приводит к гонкам данных (data race).

// Проблемный код с гонкой
var counter int
for i := 0; i < 10; i++ {
    go func() {
        counter++ // Data race!
    }()
}

Используйте:

  • Мьютексы (sync.Mutex, sync.RWMutex)
  • Атомарные операции (sync/atomic)
  • Каналы для передачи владения данными
// Корректная синхронизация
var (
    counter int
    mu      sync.Mutex
)
for i := 0; i < 10; i++ {
    go func() {
        mu.Lock()
        counter++
        mu.Unlock()
    }()
}

Планирование и блокировки

Горутины — это не потоки ОС, а легковесные потоки выполнения, управляемые рантаймом Go. Важно избегать долгих блокирующих операций без вытеснения.

Что учитывать:

  • Блокировки ввода-вывода — используйте асинхронные API
  • Долгие вычисления — периодически вызывайте runtime.Gosched()
  • Небуферизованные каналы могут создавать взаимоблокировки

Потребление памяти и рост стека

Каждая горутина начинается с небольшого стека (обычно 2KB), который динамически растёт. Однако:

  • Слишком много горутин увеличивают потребление памяти
  • Утечки в замыканиях могут удерживать большие объекты
func memoryLeak() {
    largeData := make([]byte, 10*1024*1024) // 10MB
    go func() {
        // Горутина держит ссылку на largeData, даже если он не нужен
        _ = largeData[0]
        select {} // Вечная блокировка
    }()
    // largeData не освободится, пока горутина жива
}

Логирование и отладка

Горутины усложняют отладку. Всегда добавляйте идентификаторы:

func tracedWorker(id int) {
    log.Printf("Горутина %d запущена", id)
    defer log.Printf("Горутина %d завершена", id)
    // ...
}

Ключевые рекомендации

Перед запуском горутины спросите:

  1. Как она завершится? — механизм остановки обязателен
  2. Кто будет ждать её завершения? — использование sync.WaitGroup или родительского контекста
  3. Как обрабатывать ошибки? — передача ошибок через каналы или структуры результатов
  4. Есть ли конкуренция за данные? — план синхронизации
  5. Что если она запаникует? — защита recover на верхнем уровне
  6. Не будет ли слишком много горутин? — контроль пулинга для массовых задач

Практический паттерн:

func startSupervisedWorker(ctx context.Context, wg *sync.WaitGroup, workChan <-chan Task) {
    wg.Add(1)
    go func() {
        defer wg.Done()
        defer func() {
            if r := recover(); r != nil {
                log.Printf("worker recovered from panic: %v", r)
            }
        }()
        
        for {
            select {
            case <-ctx.Done():
                return
            case task, ok := <-workChan:
                if !ok {
                    return
                }
                processTask(task)
            }
        }
    }()
}

Запуск горутины — это не просто вызов go func(). Это принятие ответственности за управление её жизненным циклом, обработку ошибок и обеспечение thread-safety. Правильное использование горутин — основа написания надёжных конкурентных программ на Go.

Что нужно учитывать при старте горутины? | PrepBro