Olá, Leandro. Como vai?
Esse comportamento que você observou é um clássico exemplo de Race Condition (Condição de Corrida) e encerramento prematuro da função principal. É ótimo ver que você avançou no curso e descobriu como solucionar, mas vale a pena detalharmos o motivo exato de isso acontecer para consolidar o aprendizado!
No código que você compartilhou, o grande problema está no fato de a goroutine que lê os dados do canal dependendo do fluxo principal para continuar viva. Quando você chama wg.Wait(), o programa principal espera que as três goroutines de busca terminem de enviar os dados para o priceChannel.
Assim que a terceira busca envia o dado para o canal, a respectiva goroutine executa o wg.Done(). Com os três Done() processados, o wg.Wait() é liberado imediatamente.
O ponto crítico está aqui: o envio de um dado para o canal e a leitura desse dado na outra goroutine acontecem de forma concorrente. Quando o wg.Wait() libera o fluxo, a função main avança para a linha do fmt.Printf("\n Tempo total: ...") e finaliza a execução de todo o programa. Se o programa principal fechar antes que a goroutine de leitura tenha tempo de processar o último item que entrou no canal, esse dado é perdido e o console exibe apenas dois preços.
Para corrigir isso de forma robusta e garantir que todos os dados sejam lidos de forma segura, a boa prática em Go é sincronizar o fechamento do canal com o término das tarefas no WaitGroup, indicando para o laço range que não haverá mais dados.
Podemos ajustar a lógica da seguinte forma:
- Deixar o
wg.Wait() e o fechamento do canal rodando em paralelo ou logo após as goroutines produtoras. - Mudar a goroutine consumidora para rodar diretamente na thread principal (tirando o
go da frente dela) para que o programa só avance após consumir tudo.
Veja um exemplo de como reestruturar essa sincronização de forma correta:
package main
import (
"buscador/internal/fetcher"
"fmt"
"sync"
"time"
)
func main() {
start := time.Now()
priceChannel := make(chan float64)
var wg sync.WaitGroup
wg.Add(3)
go func() {
defer wg.Done()
priceChannel <- fetcher.FetchPriceFromSite1()
}()
go func() {
defer wg.Done()
priceChannel <- fetcher.FetchPriceFromSite2()
}()
go func() {
defer wg.Done()
priceChannel <- fetcher.FetchPriceFromSite3()
}()
// Criamos uma goroutine separada apenas para fechar o canal quando todas as buscas terminarem
go func() {
wg.Wait()
close(priceChannel)
}()
// O loop range agora roda na thread principal, garantindo que o programa espere ler tudo antes de fechar
for price := range priceChannel {
fmt.Printf("Preço recebido: R$ %.2f \n", price)
}
fmt.Printf("\nTempo total: %s\n", time.Since(start))
}
Com essa mudança, o range sabe exatamente quando o canal foi fechado (close(priceChannel)) e encerra o laço de forma segura, garantindo que todos os 3 valores sejam exibidos na tela em 100% das execuções.
Espero que possa ter lhe ajudado!