Desafio Go

Recover em goroutine com channel

Enunciado

Implemente uma função worker(id int, jobs <-chan int, results chan<- string) que processa números recebidos do channel jobs. Para cada job, se o número for negativo, a função deve entrar em panic com a mensagem "job negativo: -X". Use defer e recover dentro da goroutine para capturar o panic e enviar uma mensagem de erro para results no formato "worker <id>: erro: job negativo: -X". Para jobs não negativos, envie "worker <id>: job <X> processado". O main deve criar 2 workers, enviar os jobs [1, -2, 3, -4, 5] e imprimir todos os resultados na ordem em que chegarem (use um WaitGroup ou um channel de sincronização).

Requisitos

  • Cada worker roda em sua própria goroutine.
  • O recover deve estar dentro de um defer na goroutine do worker.
  • O panic não deve derrubar o programa; o erro deve ser enviado para results.
  • O main deve aguardar todos os workers terminarem antes de fechar results.

Código inicial

package main

import (
	"fmt"
	"sync"
)

func worker(id int, jobs <-chan int, results chan<- string, wg *sync.WaitGroup) {
	// Seu código aqui
}

func main() {
	// Seu código aqui
}

Saída esperada

worker 1: job 1 processado
worker 2: erro: job negativo: -2
worker 1: job 3 processado
worker 2: erro: job negativo: -4
worker 1: job 5 processado
Ver dica

Use sync.WaitGroup para esperar as goroutines. Dentro do worker, um defer com recover deve enviar a mensagem de erro para results. Lembre-se de que cada worker processa jobs até o channel ser fechado.

Mostrar solução
package main

import (
	"fmt"
	"sync"
)

func worker(id int, jobs <-chan int, results chan<- string, wg *sync.WaitGroup) {
	defer wg.Done() // sinaliza que o worker terminou

	// defer com recover para capturar panics dentro da goroutine
	defer func() {
		if r := recover(); r != nil {
			// envia a mensagem de erro para o channel de resultados
			results <- fmt.Sprintf("worker %d: erro: %v", id, r)
		}
	}()

	for job := range jobs {
		if job < 0 {
			panic(fmt.Sprintf("job negativo: %d", job))
		}
		results <- fmt.Sprintf("worker %d: job %d processado", id, job)
	}
}

func main() {
	jobs := make(chan int, 5)
	results := make(chan string, 5)
	var wg sync.WaitGroup

	// Inicia 2 workers
	for i := 1; i <= 2; i++ {
		wg.Add(1)
		go worker(i, jobs, results, &wg)
	}

	// Envia jobs
	for _, j := range []int{1, -2, 3, -4, 5} {
		jobs <- j
	}
	close(jobs)

	// Aguarda os workers em uma goroutine separada
	go func() {
		wg.Wait()
		close(results)
	}()

	// Imprime resultados conforme chegam
	for res := range results {
		fmt.Println(res)
	}
}

Passo a passo

  1. A função worker recebe um id, channels jobs e results, e um *sync.WaitGroup.
  2. defer wg.Done() garante que o WaitGroup seja decrementado quando o worker terminar, mesmo em caso de panic.
  3. Um segundo defer com função anônima chama recover(). Se houver panic, envia a mensagem de erro para results.
  4. O loop for job := range jobs processa cada job. Se job < 0, chama panic com a mensagem formatada.
  5. O panic é capturado pelo defer, que envia o erro para results. O worker então retorna (não continua o loop).
  6. No main, criamos os channels jobs e results com buffer de 5.
  7. Iniciamos 2 workers com go worker(...), incrementando o WaitGroup antes de cada um.
  8. Enviamos os 5 jobs e fechamos o channel jobs.
  9. Uma goroutine separada aguarda wg.Wait() e fecha results.
  10. O loop for res := range results imprime cada resultado na ordem em que chegam.

Por que funciona

Cada worker é uma goroutine independente. O recover dentro do defer captura o panic sem afetar as outras goroutines. O WaitGroup garante que o main só feche results após todos os workers terminarem. O channel results é usado para comunicar os resultados de volta ao main de forma segura.

Erros comuns

  • Esquecer o wg.Done(): se não for chamado, o wg.Wait() nunca retorna e o programa trava.
  • Não fechar jobs: o loop for range jobs nunca termina, e os workers ficam bloqueados.
  • Recover fora do defer: se recover for chamado diretamente no corpo do worker (não dentro de um defer), ele não captura o panic e o programa quebra.
  • Panic em goroutine sem recover: se um panic ocorre em uma goroutine sem recover, todo o programa é derrubado.

Outra forma de resolver

Poderíamos usar um channel de erros separado em vez de enviar erros para results, mas isso complicaria a sincronização. A solução com recover e results é direta e demonstra o conceito.

Saída esperada

worker 1: job 1 processado
worker 2: erro: job negativo: -2
worker 1: job 3 processado
worker 2: erro: job negativo: -4
worker 1: job 5 processado