Ответ
Нет, напрямую отловить панику из дочерней горутины в родительской с помощью recover
нельзя.
Почему?
Механизм panic
/recover
работает в пределах одной горутины. Паника распространяется вверх по стеку вызовов текущей горутины, и recover
может её остановить только в том случае, если он был вызван в defer
функции в той же горутине.
Если в горутине происходит паника, и она не обрабатывается с помощью recover
, программа аварийно завершается.
Правильные подходы к обработке паники в дочерних горутинах
Ключевая идея — дочерняя горутина должна сама обработать свою панику и сообщить о ней родительской через один из механизмов синхронизации.
1. recover
+ канал для передачи ошибки
Это основной и самый надежный паттерн. Дочерняя горутина использует defer
и recover
, а в случае паники отправляет информацию о ней в виде ошибки через канал.
package main
import (
"fmt"
"time"
)
func main() {
errChan := make(chan error, 1) // Буферизированный канал
go func() {
// defer-функция для перехвата паники
defer func() {
if r := recover(); r != nil {
// Паника произошла, отправляем ее как ошибку в канал
errChan <- fmt.Errorf("дочерняя горутина запаниковала: %v", r)
}
}()
fmt.Println("Дочерняя горутина начинает работу...")
// Имитация паники
panic("что-то пошло не так")
}()
// Родительская горутина ждет сообщения из канала
select {
case err := <-errChan:
fmt.Printf("Получена ошибка от дочерней горутины: %vn", err)
case <-time.After(2 * time.Second):
fmt.Println("Дочерняя горутина завершилась без ошибок.")
}
}
2. Использование errgroup
Пакет golang.org/x/sync/errgroup
элегантно решает эту проблему. Он автоматически обрабатывает панику в любой из запущенных им горутин, превращает её в ошибку и возвращает из метода Wait()
.
package main
import (
"context"
"fmt"
"golang.org/x/sync/errgroup"
)
func main() {
g, _ := errgroup.WithContext(context.Background())
g.Go(func() error {
fmt.Println("Запускаем горутину, которая вызовет панику...")
panic("ой, все сломалось")
// Эта горутина никогда не вернет nil
return nil
})
// g.Wait() заблокируется до завершения горутины
// и вернет ошибку, если была паника или горутина вернула ошибку.
if err := g.Wait(); err != nil {
fmt.Printf("errgroup перехватил ошибку: %vn", err)
}
}
Этот подход является предпочтительным для управления группой горутин, которые могут завершиться с ошибкой.