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
recoverdeve estar dentro de umdeferna goroutine do worker. - O panic não deve derrubar o programa; o erro deve ser enviado para
results. - O
maindeve aguardar todos os workers terminarem antes de fecharresults.
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
- A função
workerrecebe umid, channelsjobseresults, e um*sync.WaitGroup. defer wg.Done()garante que o WaitGroup seja decrementado quando o worker terminar, mesmo em caso de panic.- Um segundo
defercom função anônima chamarecover(). Se houver panic, envia a mensagem de erro pararesults. - O loop
for job := range jobsprocessa cada job. Sejob < 0, chamapaniccom a mensagem formatada. - O panic é capturado pelo
defer, que envia o erro pararesults. O worker então retorna (não continua o loop). - No
main, criamos os channelsjobseresultscom buffer de 5. - Iniciamos 2 workers com
go worker(...), incrementando o WaitGroup antes de cada um. - Enviamos os 5 jobs e fechamos o channel
jobs. - Uma goroutine separada aguarda
wg.Wait()e fecharesults. - O loop
for res := range resultsimprime 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, owg.Wait()nunca retorna e o programa trava. - Não fechar
jobs: o loopfor range jobsnunca termina, e os workers ficam bloqueados. - Recover fora do defer: se
recoverfor chamado diretamente no corpo do worker (não dentro de umdefer), 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