Sobes.tech
Middle

Qual abordagem é usada para lidar com erros que ocorrem em goroutines em Go?

sobes.tech IA

Resposta da IA

Em Go, para lidar com erros em goroutines, geralmente utilizam-se as seguintes abordagens:

  • Transmissão de erros através de canais: Cria-se um canal para transmitir os valores de erro para a goroutine principal ou outra goroutine que possa processá-los.

    // Exemplo de transmissão de erro através de um canal
    func worker(id int, errors chan<- error) {
      // ... execução do trabalho
      if somethingWentWrong {
        errors <- fmt.Errorf("erro na goroutine %d", id)
        return
      }
      // ... conclusão bem-sucedida
    }
    
    func main() {
      errorCh := make(chan error, nWorkers) // Canal com buffer
      for i := 0; i < nWorkers; i++ {
        go worker(i, errorCh)
      }
    
      for i := 0; i < nWorkers; i++ {
        err := <-errorCh
        if err != nil {
          log.Printf("erro detectado: %v", err)
          // Processar erro
        }
      }
    }
    
  • Uso de sync.WaitGroup e canal de erros: sync.WaitGroup ajuda a aguardar a conclusão de todas as goroutines, e o canal é usado para coletar erros.

    // Exemplo com WaitGroup e canal de erros
    func workerWithWG(id int, wg *sync.WaitGroup, errors chan<- error) {
      defer wg.Done()
      // ... execução do trabalho
      if somethingWentWrong {
        errors <- fmt.Errorf("erro na goroutine %d", id)
      }
    }
    
    func main() {
      var wg sync.WaitGroup
      errorCh := make(chan error, nWorkers)
    
      for i := 0; i < nWorkers; i++ {
        wg.Add(1)
        go workerWithWG(i, &wg, errorCh)
      }
    
      wg.Wait()
      close(errorCh) // É importante fechar o canal após WaitGroup
    
      for err := range errorCh {
        log.Printf("erro detectado: %v", err)
        // Processar erro
      }
    }
    
  • Retornar valor e erro de uma função executada em uma goroutine: Se a goroutine realiza uma tarefa específica e pode retornar um resultado e/ou erro, ela pode ser envolvida em uma função que retorna esses valores.

    // Exemplo de retorno de valor e erro de uma função
    type result struct {
      value int
      err error
    }
    
    func doSomething(id int) (int, error) {
      // ... execução do trabalho
      if somethingWentWrong {
        return 0, fmt.Errorf("erro na goroutine %d", id)
      }
      return id * 10, nil
    }
    
    func main() {
      results := make(chan result, nWorkers)
    
      for i := 0; i < nWorkers; i++ {
        go func(idx int) {
          val, err := doSomething(idx)
          results <- result{val, err}
        }(i)
      }
    
      for i := 0; i < nWorkers; i++ {
        res := <-results
        if res.err != nil {
          log.Printf("erro detectado: %v", res.err)
          // Processar erro
        } else {
          log.Printf("resultado: %d", res.value)
        }
      }
    }
    
  • Uso de contexto (context.Context) para cancelar e lidar com erros: O contexto pode ser usado para sinalizar o cancelamento de goroutines ou para transmitir erro para baixo na hierarquia de chamadas.

    // Exemplo com contexto
    func workerWithContext(ctx context.Context, id int, errors chan<- error) {
      select {
      case <-ctx.Done():
        errors <- fmt.Errorf("goroutine %d cancelada: %v", id, ctx.Err())
        return
      default:
        // ... execução do trabalho
        if somethingWentWrong {
          errors <- fmt.Errorf("erro na goroutine %d", id)
        }
      }
    }
    
    func main() {
      ctx, cancel := context.WithCancel(context.Background())
      errorCh := make(chan error, nWorkers)
    
      for i := 0; i < nWorkers; i++ {
        go workerWithContext(ctx, i, errorCh)
      }
    
      // Em algum momento, pode-se chamar cancel() para cancelar
      // cancel()
    
      // Coleta de erros
      go func() {
        for err := range errorCh {
          log.Printf("erro detectado: %v", err)
        }
      }()
    
      // Esperar terminar ou outra lógica
      // ...
    }
    

A escolha de uma abordagem específica depende dos requisitos de tratamento de erros e da arquitetura da aplicação.