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

Как защитить горутину от зависания?

1.2 Junior🔥 171 комментариев
#Конкурентность и горутины

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

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

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

Защита горутин от зависания в Go

Зависание (stalling) горутины — ситуация, когда горутина бесконечно ожидает ресурс или выполняет операцию без прогресса. Это приводит к утечкам памяти, дедлокам и снижению производительности приложения. Вот комплексный подход к защите.

1. Использование контекстов (Context) для отмены операций

Контексты — основной механизм для передачи сигналов отмены и дедлайнов между горутинами.

package main

import (
    "context"
    "time"
)

func worker(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            // Освобождаем ресурсы и завершаемся
            return
        default:
            // Выполняем полезную работу
            performTask()
        }
    }
}

func main() {
    // Контекст с таймаутом
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    
    go worker(ctx)
    // ... остальной код
}

2. Таймауты и дедлайны для операций ввода-вывода

Все блокирующие операции должны иметь ограничения по времени:

// Таймаут на чтение из канала
select {
case result := <-ch:
    // Обработка результата
case <-time.After(3 * time.Second):
    // Таймаут - горутина не должна зависнуть
    log.Println("Таймаут чтения из канала")
}

// Таймаут на сетевую операцию
conn.SetReadDeadline(time.Now().Add(2 * time.Second))

3. Применение шаблона Worker Pool с ограничением

Ограничение количества одновременно работающих горутин:

func processWithPool(tasks []Task, maxWorkers int) {
    sem := make(chan struct{}, maxWorkers)
    var wg sync.WaitGroup
    
    for _, task := range tasks {
        sem <- struct{}{} // Захват слота
        wg.Add(1)
        
        go func(t Task) {
            defer func() {
                <-sem // Освобождение слота
                wg.Done()
            }()
            
            // Защищенный вызов
            processTask(t)
        }(task)
    }
    
    wg.Wait()
}

4. Мониторинг и health checks

Реализация периодической проверки работоспособности:

func monitoredGoroutine(stopCh <-chan struct{}) {
    heartbeat := time.NewTicker(1 * time.Second)
    defer heartbeat.Stop()
    
    go func() {
        for {
            select {
            case <-heartbeat.C:
                // Отправляем сигнал "живости"
                reportHeartbeat()
            case <-stopCh:
                return
            }
        }
    }()
    
    // Основная логика горутины
    // ...
}

5. Защита от паники (panic recovery)

Обязательная обработка паник в горутинах:

func safeGoroutine() {
    defer func() {
        if r := recover(); r != nil {
            log.Printf("Восстановлено после паники: %v", r)
            // Здесь можно перезапустить горутину при необходимости
        }
    }()
    
    // Потенциально опасный код
    riskyOperation()
}

6. Избегание дедлоков

Ключевые практики:

  • Упорядоченный захват мьютексов — всегда захватывайте мьютексы в одинаковом порядке
  • Использование sync.RWMutex для read-heavy workloads
  • Минимизация времени удержания блокировок
// ПРАВИЛЬНО: одинаковый порядок захвата
var mu1, mu2 sync.Mutex

func correctOrder() {
    mu1.Lock()
    defer mu1.Unlock()
    
    mu2.Lock()
    defer mu2.Unlock()
}

7. Использование select с default для неблокирующих операций

func nonBlockingSend(ch chan<- Data, data Data) {
    select {
    case ch <- data:
        // Успешная отправка
    default:
        // Канал заполнен - альтернативная обработка
        handleFullChannel(data)
    }
}

8. Профилирование и инструментирование

Используйте встроенные инструменты Go:

  • pprof для анализа блокировок и горутин
  • trace для изучения временных характеристик
  • expvar для мониторинга метрик в runtime

Комплексная стратегия защиты

Для надежной защиты комбинируйте несколько подходов:

  1. На этапе проектирования:

    • Определите timeout-ы для всех внешних вызовов
    • Спроектируйте graceful shutdown
    • Ограничьте параллелизм
  2. В реализации:

    • Всегда используйте context.Context
    • Добавляйте recovery в длительные горутины
    • Реализуйте heartbeat механизмы
  3. В эксплуатации:

    • Настройте мониторинг количества горутин
    • Используйте алерты по продолжительности операций
    • Регулярно анализируйте дампы горутин

Важное замечание: Не существует серебряной пули — защита от зависаний требует системного подхода на всех уровнях приложения, от архитектуры до мониторинга в production.