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

Можно ли отлавливать панику дочерней горутины в родительской горутине?

2.0 Middle🔥 192 комментариев
#Основы Go

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

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

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

Можно ли отлавливать панику дочерней горутины в родительской горутине?

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

1. Использование recover() внутри дочерней горутины

Основной способ — вызвать recover() внутри самой дочерней горутины, обработать панику локально и уведомить родительскую горутину через каналы или другие механизмы синхронизации.

package main

import (
    "fmt"
    "time"
)

func childGoroutine(resultChan chan<- string, panicChan chan<- error) {
    defer func() {
        if r := recover(); r != nil {
            // Отправляем информацию о панике в родительскую горутину
            panicChan <- fmt.Errorf("паника в дочерней горутине: %v", r)
        }
    }()
    
    // Имитация паники
    panic("внезапная ошибка в дочерней горутине")
    
    resultChan <- "успех" // Этот код не выполнится
}

func main() {
    resultChan := make(chan string)
    panicChan := make(chan error)
    
    go childGoroutine(resultChan, panicChan)
    
    select {
    case result := <-resultChan:
        fmt.Println("Результат:", result)
    case err := <-panicChan:
        fmt.Println("Обнаружена паника:", err)
    case <-time.After(2 * time.Second):
        fmt.Println("Таймаут горутины")
    }
}

2. Обёртка для безопасного выполнения горутин

Для упрощения обработки паник часто создают обёртку, которая запускает горутины с автоматическим восстановлением.

func safeGo(f func(), panicHandler func(interface{})) {
    go func() {
        defer func() {
            if r := recover(); r != nil {
                panicHandler(r)
            }
        }()
        f()
    }()
}

func main() {
    safeGo(
        func() {
            panic("критическая ошибка")
        },
        func(r interface{}) {
            fmt.Printf("Перехвачена паника: %v\n", r)
            // Логирование, уведомление или другие действия
        },
    )
    
    time.Sleep(100 * time.Millisecond)
    fmt.Println("Основная горутина продолжает работу")
}

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

В сочетании с обработкой паник полезно применять context.Context для координации отмены операций при возникновении ошибок в дочерних горутинах.

func worker(ctx context.Context, resultChan chan<- int) {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Восстановление после паники:", r)
            // Можно отправить сигнал об ошибке в родительскую горутину
            resultChan <- -1
        }
    }()
    
    select {
    case <-ctx.Done():
        fmt.Println("Работа отменена")
        return
    default:
        // Имитация работы
        panic("ошибка в worker")
    }
}

4. Паттерн с обработчиком ошибок через каналы

Создание канала для ошибок позволяет централизованно обрабатывать сбои из нескольких горутин.

func processData(data int, errChan chan<- error) {
    defer func() {
        if r := recover(); r != nil {
            errChan <- fmt.Errorf("паника при обработке данных %d: %v", data, r)
        }
    }()
    
    if data == 0 {
        panic("деление на ноль")
    }
    _ = 100 / data
}

func main() {
    errChan := make(chan error, 3)
    for i := 0; i < 3; i++ {
        go processData(i, errChan)
    }
    
    for i := 0; i < 3; i++ {
        if err := <-errChan; err != nil {
            fmt.Println("Ошибка:", err)
        }
    }
}

Важные ограничения и лучшие практики

  • recover() работает только в той же горутине, где произошла паника. Вызов recover() в родительской горутине не перехватит панику дочерней.
  • Необработанная паника в любой горутине приведёт к аварийному завершению всей программы.
  • Всегда используйте defer с recover() в горутинах, которые могут паниковать.
  • Предпочитайте возврат ошибок через каналы вместо паник, так как это делает поток управления более предсказуемым.
  • Для сложных приложений рассмотрите использование шаблона supervisor или готовых решений вроде errgroup из пакета golang.org/x/sync.

Заключение

Хотя прямая передача паники между горутинами невозможна, комбинируя defer, recover(), каналы и контексты, можно эффективно обнаруживать и обрабатывать сбои в дочерних горутинах, обеспечивая отказоустойчивость приложения. Ключевой принцип — каждая горутина должна самостоятельно обрабатывать свои паники и явно сообщать о них другим частям программы через механизмы связи.

Можно ли отлавливать панику дочерней горутины в родительской горутине? | PrepBro