Importante

Você está vendo a versão anterior da nova experiência da Alura que estamos preparando para você. Em breve, ela ganha uma identidade visual novinha totalmente pensada em potencializar seus estudos!

1
resposta

[Dúvida] Programa imprime 2 preços apenas, quando eram para ser 3.

Bom, tive minha dúvida solucionada ao decorrer do curso, ela foi explicada na aula 2. Melhorando o código

Segui exatamente o código passado na video aula, porém me deparei com a seguinte questão ao tentar executar varias vezes:

O programa me devolve 3 preços as vezes mas na maioria dos casos me devolve apenas 2, o que pode estar acontecendo?


package main

import (
    "buscador/internal/fetcher"
    "fmt"
    "sync"
    "time"
)

func main() {
    start := time.Now()
    priceChannel := make(chan float64)
    var wg sync.WaitGroup // WaitGroup
    wg.Add(3)

    // Função depende da conclusão das funções de preço
    go func() {
        for price := range priceChannel {
            fmt.Printf("Preço recebido: R$ %.2f \n", price)
        }
    }()

    go func() {
        defer wg.Done()
        priceChannel <- fetcher.FetchPriceFromSite1()
    }()

    go func() {
        defer wg.Done()
        priceChannel <- fetcher.FetchPriceFromSite2()
    }()

    go func() {
        defer wg.Done()
        priceChannel <- fetcher.FetchPriceFromSite3()
    }()

    wg.Wait()

    fmt.Printf("\n Tempo total: %s", time.Since(start))
}

Evidenciando o código usado aqui, pode ser que seja um erro meu

Print do terminal mostrando casos em que estão sendo impressos 3 e 2 preços

1 resposta

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!